Facebook Login Flow & Best Practices

Best practice for Facebook login flow with the JavaScript SDK and PHP SDK v4.1

Jan 09, 2015

I get a lot of people asking me about the best practice for Facebook login flow. Most of us are used to logging people in using an email address and a hash of their password, but how do you log a user in from Facebook when they never enter a password on your site?

Let's look at the best practices for logging a Facebook user into your web app.

Note: As of writing (Jan 9, 2015) the latest stable version of the Facebook PHP SDK is v4.0. Once v4.1 is released (probably within a month or so), it will have a very different implementation. For this reason, all of the examples included in this post are for v4.1 of the SDK which is currently still in development mode.

Create A Facebook App

If you haven't already, become a Facebook developer and create an app.

You'll need to configure your app to use the "website" platform. To do this you'll need to provide the root URL of your web app.

Make sure to take note of the app ID and app secret because you'll need those later on.

Understanding User Permissions

Your app will need to ask the user to grant your app certain permissions. The minium permission you can ask a user for is the public_profile permission which grants your app access to the user's public profile. This access is very limited and does not give you access to the user's email or friends list.

Here are a few examples of actions that will require user approval:

Meet The Graph API

Before you log a user in, you'll need to understand where the user's information is coming from. You can perform CRUD (create, read, update, delete) operations against data on Facebook via the Graph API.

Tip: Facebook provides a neat little tool called the Graph API explorer which allows you to play with features of the Graph API within the context of a nice GUI.

Understanding the Graph API is a fairly big subject. Here's a TL;DR on the Graph API:

  • The Graph API lives at https://graph.facebook.com.
  • The Graph API is versioned with a URL-prefix. For example, v2.2 lives athttps://graph.facebook.com/v2.2. Un-prefixed URL's will default to the oldest supported version of the API.
  • The Graph API is not truly RESTful.
  • Users are authenticated on the Graph API using OAuth 2.0.
  • Every "thing" on Facebook (like a User, Comment, Page, Photo, etc) is known as a "node" and has a corresponding id field.
  • Every relationship between one or more nodes on Facebook (such as likes, comments, photos, etc) is know as an "edge".
  • Graph endpoints are designed using the node ID at the root (after the Graph version prefix described above): /{node-id}
  • Accessing relationships between nodes is accessed via the edge name:/{node-id}/edge_name. Getting a list of comments on a photo would look like/{photo-id}/comments.
  • The /me endpoint is a special endpoint that refers to the User or Page that is making the request. If you use a user access token to make a GET request to /me, Graph will return a User node. If you use a page access token to make a GET request to /me, a Page node will be returned.

A Note On Graph Endpoints: Some people read the Graph API reference docs and erroneously assume that since the docs refer to named endpoints that you use the name of the endpoint in your request like /node/{node-id}. You don't need to use the name of the node in the endpoint. Using the /comment endpoint as an example, the endpoint would just contain the comment ID/{comment-id} and would not be prefixed with the name of the endpoint in the documentation /comment/{comment-id}.

How To Log A User In

Facebook users are authenticated via the Graph API using OAuth. If OAuth sounds scary to you, no worries, there are tools to help you authenticate a user without knowing how OAuth works.

For a web app, there are two ways to log a Facebook user into your site.

  1. Using the JavaScript SDK (easiest)
  2. Manual OAuth 2.0 authentication

Manual OAuth 2.0 authentication can be done using the Facebook PHP SDK v4.1 which we'll discuss below.

Logging in with the JavaScript SDK

You can authenticate a Facebook user using just a few lines of JavaScript with the JavaScript SDK. The SDK does all the OAuth heavy lifting behind the scenes for you.

Once you've included the required JavaScript snippet in your HTML to load the SDK, you can make use of FB.login() to log the user in.

Configuring the JavaScript SDK

In your initialization snippet, I recommend setting the cookie option to true. This will tell the JavaScript SDK to set a cookie on your domain with information about the user. This will allow you to access the authentication data in the back-end later on using the PHP SDK for example. For more on this see, "Using The JavaScript SDK And PHP SDK Together" below.

FB.init({
appId : '{your-app-id}',
cookie : true,
version : 'v2.2'
});

Grabbing User Data From A Signed Request

The cookie that the JavaScript SDK created contains a signed request.

A signed request contains a payload of data about the user who authorized your app. The payload is delivered as a base64-encoded JSON string that has been escaped for use in a URL. It also contains a signature hash to validate against to ensure the data is coming from Facebook.

The cookie is named fbsr_{your-app-id} and it contains data that looks something like this:

CBVDhaIKcEfoKiL-Hxu6TONsY62edUMdIVHhszTBxcI=.eyJoZWxsbyI6IllvdSBhcmUgYSBzbWFydCBjb29raWUgZm9yIGZpbmRpbmcgdGhpcyEgTG92ZSwgU2FtbXlLIiwiYWxnb3JpdGhtIjoiSE1BQy1TSEEyNTYiLCJpc3N1ZWRfYXQiOjE0MjA3MzcxMTh9

That gibberish is the signed request.

Extra credit: The signed request above is one I made myself using the app secret foo_secret. See if you can decode & validate the payload to see a hidden message. If you succeed, let me know!

Making Graph Requests in JavaScript

The JavaScript SDK provides a method called, FB.api() which sends requests to the Graph API on behalf of the logged in user.

In this example we grab the idname and email of the logged in user. This assumes we've already logged the user in with FB.login().

FB.api('/me?fields=id,name,email', function(response) {
console.log(response);
});

Alternatively, we could have written the same request like so:

FB.api('/me', {fields: 'id,name,email'}, function(response) {
console.log(response);
});

Note that we didn't specify an access_token parameter. We could have specified one, but the JavaScript SDK will fallback to the access token of the authenticated user.

Logging in with the PHP SDK v4.1

Another way of logging a user in with Facebook is by using the Facebook PHP SDK v4.1.

Documentation on v4.1: You can see the most up-to-date docs for v4.1 in the GitHub repo since as of writing v4.1 has not yet been officially released and the documentation hasn't been updated on Facebook's documentation site.

The Flow For Manual Login With Redirect URL's

If we didn't have the PHP SDK, we would manually log a user in using the following flow:

  1. Present a user with a special link to log in with Facebook.
  2. The user clicks the link and is taken to www.facebook.com where they are immediately prompted with a dialog asking them to grant your app access to whatever permissions you wanted to ask for.
  3. After the user responds (accept or reject), they will be redirected back to the callback URL you specified in the login link. The callback URL will contain several GET parameters.
  4. If the user accepted the request the response will contain a code GET param (which can be exchanged for an access token).
  5. If the user rejected the request a number of "error" GET params (errorerror_code,error_reason, & error_description) will be returned.
  6. state GET param will also be returned containing the cryptographically-secure random string you originally generated in the login URL to validate against CSRFattacks and ensure that the response came from Facebook.

Many of those steps are handled by the PHP SDK for you. So you don't have to worry about generating and validating the CSRF or exchanging the code for an access token for example.

Installing the PHP SDK v4.1 With Composer

If you're using composer, you can just add the SDK to your composer.json file.

{
"require": {
"facebook/php-sdk-v4": "~4.1.0@dev"
}
}

Normally you wouldn't want to use the @dev stability flag but since v4.1 of the SDK has not been released yet, it's still in development mode. Composer won't install packages in development mode by default so you'll need to allow the minium stability of "development"before Composer will installed it. After the v4.1 is officially released (I'm going to guess end of Feb 2015?) you should be able to install it without setting the stability flag to @dev.

{
"require": {
"facebook/php-sdk-v4": "~4.1.0"
}
}

A Note About Versioning: The Facebook PHP SDK does not followsemantic versioning so major releases happen at the second level of the version number. The release versions look like:4.MAJOR.MINOR|PATCH. So if you were to set the version number to"~4.1" in your composer.json, the next major release v4.2 with breaking changes would automatically be upgraded when you runcomposer update. To prevent this, make sure to include the version number down to MINOR|PATCH"~4.1.0".

Installing the PHP SDK v4.1 Without Composer

If you're not using Composer (you're still not using Composer!?), you can manually download the code, unzip it and include the autoloader.

require('/path/to/src/Facebook/autoload.php');

Initializing The PHP SDK

Grab your app ID and app secret from the app dashboard and use them to instantiate a newFacebook\Facebook() service object.

$fb = new Facebook\Facebook([
'app_id' => '{app-id}',
'app_secret' => '{app-secret}',
]);

Generating The Login Link

The login link contains a number of key parameters including:

  1. The app ID in the client_id param
  2. The state param which is a cryptographically-secure random string to validate againstCSRF attacks
  3. The callback URL param redirect_uri which is where the user will be redirected to after the user responds to the app authentication request
  4. The scope param which is a comma-separated list of permissions

...and a few others. The SDK makes it easy to generate the login with theFacebookRedirectLoginHelper.

# login.php

// Get the FacebookRedirectLoginHelper
$helper = $fb->getRedirectLoginHelper(); $permissions = ['email', 'user_likes']; // optional
$loginUrl = $helper->getLoginUrl('https://{your-website}/login-callback.php', $permissions); echo '<a href="' . $loginUrl . '">Log in with Facebook!</a>';

Callback URL: The callback URL that you specify such ashttps://{your-website}/ in the example above, needs to match the URL you provided in your Facebook app settings.

Handling The Callback Response

In the callback URL /login-callback.php you can do a check for a successful response and obtain the access token; otherwise display an error.

# login-callback.php

// Get the FacebookRedirectLoginHelper
$helper = $fb->getRedirectLoginHelper();
// @TODO This is going away soon
$facebookClient = $fb->getClient(); try {
$accessToken = $helper->getAccessToken($facebookClient);
} catch(Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
} if (isset($accessToken)) {
// Logged in
// Store the $accessToken in a PHP session
// You can also set the user as "logged in" at this point
} elseif ($helper->getError()) {
// There was an error (user probably rejected the request)
echo '<p>Error: ' . $helper->getError();
echo '<p>Code: ' . $helper->getErrorCode();
echo '<p>Reason: ' . $helper->getErrorReason();
echo '<p>Description: ' . $helper->getErrorDescription();
exit;
}

The Access Token Is Not A String: The PHP SDK returns the access token as a Facebook\AccessToken entity which contains the full access token, the expiration date & machine ID if it exists. It also contains a number of methods to easily manage access tokens. To get the access token as a string, you can type cast it using the(string) syntax: $tokenAsString = (string) $accessToken;. The Facebook\AccessToken entity can also be serialize()'ed to maintain all of the original data.

After you obtain a user access token, you can just store it in a PHP session (either serialized or as a string) to make requests to the Graph API on behalf of the user.

This is also the point in which you would mark the user as "logged in" in your web framework (see "Managing The User's 'Logged In' State With The Web Framework" below).

Setting The Default Access Token

Once you have an access token stored in a PHP session or in your database, you can set it as the default fallback access token in the constructor of the Facebook\Facebook() service class. The default access token will be used as a fallback access token if one is not provided for a specific request.

$fb = new Facebook\Facebook([
'app_id' => '{app-id}',
'app_secret' => '{app-secret}',
'default_access_token' => '{default-access-token}',
]);

Alternatively if you already have an instance of Facebook\Facebook(), you can set the default fallback access token using the setDefaultAccessToken() method.

$fb->setDefaultAccessToken('{default-access-token}');

Making Graph Requests With The PHP SDK v4.1

The PHP SDK supports GETPOST, & DELETE requests using the get()post(), &delete() methods respectfully.

In this example we grab the idname and email of the logged in user. This assumes we've already obtained an access token for the user.

try {
// Returns a `Facebook\FacebookResponse` object
$response = $fb->get('/me?fields=id,name,email', '{access-token}');
} catch(Facebook\Exceptions\FacebookResponseException $e) {
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
} // Returns a `Facebook\GraphNodes\GraphUser` collection
$user = $response->getGraphUser(); echo 'Name: ' . $user['name'];
// OR
// echo 'Name: ' . $user->getName();

There's a lot going on behind the scenes so let's explain step-by-step:

  1. The get() method uses {access-token} to obtain the endpoint /me. If{access-token} wasn't specified, the default fallback access token would be used if it was set as described above.
  2. FacebookResponseException is thrown if there was an error response from the Graph API.
  3. FacebookSDKException is thrown if there was an error building the request (like unable to read a local file that was set for upload for example).
  4. The get() method returns a Facebook\FacebookResponse object which represents an HTTP response. This contains methods for debugging the response likegetHttpStatusCode() and getBody().
  5. The SDK does a really great job of analyzing the JSON response from Graph and converting it into something useful like a GraphObject collection. To convert the response into a GraphObject collection we use $user = $response->getGraphUser().
  6. The $user variable is an instance of the Facebook\GraphNodes\GraphUser collection which has handy methods like getEmail() and getHometown().

Using the JavaScript SDK and PHP SDK together

We could code all of our Facebook integration stuff in just JavaScript or just PHP, but if we use the JavaScript SDK and PHP SDK's in tandem we get a bit more power and flexibility.

Login With JavaScript And Get Access Token With PHP

It's very common to log a user in with the JavaScript SDK and then grab the access token it generates with the PHP SDK. This is a better experience for the user as the login dialog is displayed directly on top of your web app and the user is never redirected to the Facebook website.

After you've logged a user in with the JavaScript SDK (as described above), you can usewindow.location.replace("fb-login-callback.php"); to redirect them to a callback URL. In the callback URL you can make use of the FacebookJavaScriptHelper in the Facebook PHP SDK to obtain the access token from the cookie that was set by the JavaScript SDK.

# fb-login-callback.php
$jsHelper = $fb->getJavaScriptHelper();
// @TODO This is going away soon
$facebookClient = $fb->getClient(); try {
$accessToken = $jsHelper->getAccessToken($facebookClient);
} catch(Facebook\Exceptions\FacebookResponseException $e) {
// When Graph returns an error
echo 'Graph returned an error: ' . $e->getMessage();
} catch(Facebook\Exceptions\FacebookSDKException $e) {
// When validation fails or other local issues
echo 'Facebook SDK returned an error: ' . $e->getMessage();
} if (isset($accessToken)) {
// Logged in.
} else {
// Unable to read JavaScript SDK cookie
}

Troubleshooting: If the $jsHelper->getAccessToken() is just returning null and not throwing any exceptions, that means the cookie from the JavaScript SDK is not set. Make sure you have the option cookie: true in your FB.init() as described earier. If this is not set, the JavaScript SDK will not save a cookie on your site.

Managing The User's "Logged In" State With The Web Framework

When logging a user in with Facebook, it can be confusing to know how and when to mark a user as "logged in" within the context of your web framework (Laravel, Symfony, a custom framework, etc).

The Login States

There are three login states of a user on Facebook:

  • Not logged into Facebook
  • Logged into Facebook but have not authorized your app
  • Logged into Facebook and have authorized your app

And in your own web framework you have two states (more or less):

  • Logged in
  • Not logged in

It gets even hairier when we start working with extended permissions. When a user logs in they can choose not to grant certain permissions while still granting other permissions. And a user can always revoke specific permissions in the future or even revoke your whole app (which you can track using a deauthorize callback URL).

So how do we juggle all those states? Simple - boil them down into two states: "logged in & "not logged in".

Determining If User Is "Logged In"

In a typical web app, we would would determine that the user was logged in by matching their email/username and a hash of their password in the database. If a match is found, the user's status is set to "logged in" with the web framework.

The JavaScript SDK and PHP SDK have all the proper data & communication validation features to ensure that the "login with Facebook" process is secure and coming from Facebook. So as long as we use the official SDK's to log the user in we can be sure that once we obtain the user access token, we can mark that user as "logged in" in our web framework.

We just need to check the database for their Facebook user ID - no need to check passwords or store & validate the access token or anything like that. (See "Saving The Facebook User ID In The Database" below).

Logging A User Out

The JavaScript SDK provides a method to log a user out called FB.logout(). And the PHP SDK provides a method to generate a logout URL called getLogoutUrl().

It's important to note that these logout methods will revoke the user's access token for your app and also log the user out of Facebook. So it's not common to use this functionality as most people like to just stay logged into Facebook all the time.

Plus it would be weird that every time a user logged out of your web app, "Acme, Inc." they also get logged out of Facebook.

So all you need to do to log a user out is log them out using your web framework (like deleting the session or whatever logout convention your web framework uses). You don't need to tell Facebook about the log out action in any way.

Saving The User In The Database

Deciding what information about a user to save in the database isn't always trivial. And not to mention Facebook requires that you give users control of the data you store about themand that the data you store isn't used improperly.

Saving The Facebook User ID In The Database

After you've authenticated a user, you will have access to their Facebook user ID which has been scoped to your Facebook app. At minimun you'll want to store the user's Facebook ID in your database and the user ID is the only thing you'll need to store in the database to authenticate a user with your web framework.

Watch Out For Big Numbers: User ID's on Facebook are really big. Bigger than a normal signed INT in most cases. So you'll want to store the user ID as an unsigned BIGINT in your database. I've seen developers use an indexed VARCHAR 255 to store the Facebook ID's as well. In any case, make sure to avoid a plain-old signed INT to store the user ID as that will cause some unexpected behavior. Learn more about signed vs unsigned.

My usual flow for logging a user in and saving their ID in the database goes something like this:

  1. Obtain a user access token (via the JavaScript or PHP SDK).
  2. Grab the user id and any other user data, e.g. name & email from the Graph API using the user access token.
  3. Check the database for the existence of the user ID.
  4. New Users: If the user ID cannot be found in the datase, create a new entry for the user in the database using the data you obtained from Graph in step 2.
  5. Returning Users: If the user ID is found in the datase, this is a returning user.
  6. Set the user's log-in status as "logged in" with the web framework.

Saving The User Access Token In The Database

You don't need to save the user access token in the database unless you plan on making requests to the Graph API on the user's behalf later on when the user is not actively using your web app.

Access Tokens Don't Last Long: By default an access token will only last about 2 hours so you'll want to extend the short-lived access token with a long-lived access token. Then you can save the long-lived access token in your database.

Asking For More Permissions

It's a best practice to ask for the bare minimum permissions that your app needs to function when a user first logs into your app. You'll get more people logging into your app if you do this.

If you have a feature that posts something to a user's timeline on their behalf, for example, you'll need the publish_actions permission. This is an ugly permission to ask for and most people (myself included) won't grant an app permission to post on their behalf - especially if the app asks for it right up front.

So just initially log the user in with the most basic permissions you need and then when you need to use the feature that posts on the user's behalf, ask the user for thepublish_actions permission.

To ask for new permissions, go through one of the login processes above with thepublish_actions permission in the scope list.

Update your access token! Since access tokens are tied to the permissions the user granted your app, you'll need to use the new access token returned from Facebook after requesting additional permissions from the user.

Outro

Logging users in with Facebook is pretty easy with the JavaScript SDK, but knowing what to do after the user granted your app permissions isn't exactly trivial.

Once you wrap your head around what it means for a user to be "logged in" to your app, things should become much easier for you.

Good luck with integrating Facebook Login into your web app! And if you were able to decode the signed request I created above, let me know you cracked it!

If you found this guide helpful, say, "Hi" on twitter! I'd love to hear from you. :)

Need more help?

I'm available for one-on-one help!

php and js to facebook登陆 最佳实践的更多相关文章

  1. 十个书写Node.js REST API的最佳实践(上)

    收录待用,修改转载已取得腾讯云授权 原文:10 Best Practices for Writing Node.js REST APIs 我们会通过本文介绍下书写Node.js REST API的最佳 ...

  2. 十个书写Node.js REST API的最佳实践(下)

    收录待用,修改转载已取得腾讯云授权 5. 对你的Node.js REST API进行黑盒测试 测试你的REST API最好的方法之一就是把它们当成黑盒对待. 黑盒测试是一种测试方法,通过这种方法无需知 ...

  3. 编写 Node.js Rest API 的 10 个最佳实践

    Node.js 除了用来编写 WEB 应用之外,还可以用来编写 API 服务,我们在本文中会介绍编写 Node.js Rest API 的最佳实践,包括如何命名路由.进行认证和测试等话题,内容摘要如下 ...

  4. require.js 最佳实践

    require.js是一个js库,相关的基础知识,前面转载了两篇博文:Javascript模块化编程(require.js), Javascript模块化工具require.js教程,RequireJ ...

  5. JavaScript best practices JS最佳实践

    JavaScript best practices JS最佳实践 0 简介 最佳实践起初比较棘手,但最终会让你发现这是非常明智之举. 1.合理命名方法及变量名,简洁且可读 var someItem = ...

  6. vue.js+boostrap最佳实践

    一.为什么要写这篇文章 最近忙里偷闲学了一下vue.js,同时也复习了一下boostrap,发现这两种东西如果同时运用到一起,可以发挥很强大的作用,boostrap优雅的样式和丰富的组件使得页面开发变 ...

  7. Atitit. js mvc 总结(2)----angular 跟 Knockout o99 最佳实践

    Atitit. js mvc 总结(2)----angular  跟 Knockout o99 最佳实践 1. 框架 angular 跟Knockout 1 2. 2. 简单的列表绑定:Knockou ...

  8. JS编程最佳实践

    最近花了一周时间把<编写可维护的js> 阅读了一遍, 现将全书提到的JS编程最佳实践总结如下, 已追来者! 1.return 之后不可直接换行, 否则会导致ASI(自动分号插入机制)会在r ...

  9. 《.NET最佳实践》与Ext JS/Touch的团队开发

    概述 持续集成 编码规范 测试 小结 概述 有不少开发人员都问过我,Ext JS/Touch是否支持团队开发?对于这个问题,我可以毫不犹豫的回答:支持.原因是在Sencha官网博客中客户示例中,有不少 ...

随机推荐

  1. 转载文章 208 个最常见 Java 面试题全解析

    最近正值春招,一直在给公司招聘 Java 程序员,我从 2015 年做 TeamLeader 开始就习惯性地收集平时遇到的 Java 技术问题或周围朋友见过的面试题,经过不断筛选,终于凝练成一套实用的 ...

  2. Q4m使用手册

    q4m是基于mysql存储引擎的轻量级消息队列,通过扩展SQL语法来操作消息队列,使用简单,容易上手,开发人员基本不用再进行学习和熟悉.Q4M支持多发送方,多接收方,接收方相互不影响,php项目中异步 ...

  3. [ActionScript 3.0] as3启动QQ

    import flash.html.HTMLLoader; import flash.net.URLLoader; import flash.net.URLRequest; import flash. ...

  4. [iOS笔试600题]二、常识篇(共有72题)

    [B]1.NSObject是一个根类,几乎所有的类都是从它派生而来.但是根类并不拥有真它类都有的alloc和init方法?[判断题] A. 正确 B. 错误 [A]2. UIResponder可以让继 ...

  5. iOS学习笔记(3)--初识UINavigationController(无storyboard)

    纯代码创建导航控制器UINavigationController 在Xcode6.1中创建single view application的项目,删除Main.storyboard文件,删除info.p ...

  6. 初识gulp

    之前一段时间学习使用了gulp自动化构建工具,并在现在使用的项目上部署使用,同时在这做个笔记进行小结,以便加深记忆,如有理解错误的地方请不吝赐教 gulp 的解释我就不多说了 这里引用官网的一句话   ...

  7. P4915 帕秋莉的魔导书

    题目链接 题意分析 当前等级为\(x\)的魔法书会对等级在\([x,inf]\)的所有人造成\(y\)的影响 所以除了求平均值之外 就是区间修改区间求和 需要使用动态开点 + 标记永久化 需要注意的是 ...

  8. Flink--Streaming Connectors

    原网址:https://ci.apache.org/projects/flink/flink-docs-release-1.7/dev/connectors/ Predefined Sources a ...

  9. C# ListView用法详解 很完整

    一.ListView类 1.常用的基本属性: (1)FullRowSelect:设置是否行选择模式.(默认为false) 提示:只有在Details视图该属性才有意义. (2) GridLines:设 ...

  10. net 反编译神器

    文章地址:https://www.cnblogs.com/sheng-jie/p/10168411.html dnSpy官网下载  分享链接 .net core源码导航 https://www.cnb ...