To add Facebook Login to a website or Django app in 2026, use Facebook Login built on OAuth 2.0 together with the Graph API. Register an app in the Meta for Developers dashboard, add the Facebook Login product with an exact OAuth redirect URI, send the user to the versioned login dialog (https://www.facebook.com/v19.0/dialog/oauth), exchange the returned code for an access token at https://graph.facebook.com/v19.0/oauth/access_token, then call GET /v19.0/me?fields=id,name,email to read the profile. The old PHP SDK, FQL, FBML, and unversioned Graph API calls from pre-2018 tutorials no longer work — the Graph API is now versioned and rolling-sunset, and reading a user's email for the public requires the email permission plus App Review.
Key takeaways
- OAuth 2.0 is the only login flow. Always pin a Graph API version (for example
v19.0); Facebook sunsets old versions on a rolling ~2-year schedule, so unversioned calls eventually break. public_profileis free; almost everything else needs App Review. There are two tiers — Standard Access (only app-role users can log in) and Advanced Access (the public), which App Review unlocks.- Email requires the
emailpermission plus App Review to read for people who are not admins, developers, or testers of your app. - Friends, Pages, and Groups data is heavily restricted now.
user_friendsreturns only friends who also use your app,manage_pageswas split into granularpages_*permissions, anduser_groupswas removed. - You must provide a data deletion callback. Meta requires a Data Deletion Request URL or callback for any app that stores user data.
- Use django-allauth's
facebookprovider for production sign-in instead of hand-rolling the handshake. - Everything legacy is dead: the PHP SDK, FQL, FBML, and unversioned
graph.facebook.comcalls. Userequestson Python 3.
What changed since the old Facebook integration guide?
If you followed a pre-2018 tutorial (including the original version of this post), every step now fails. Facebook moved to a versioned Graph API, retired its server SDKs and query languages, and put nearly all data behind App Review. Here is the old-vs-current mapping.
| Aspect | Old (this article's original, ~2015) | Current (2026) |
|---|---|---|
| Login dialog | graph.facebook.com/oauth/authorize (unversioned) |
facebook.com/v19.0/dialog/oauth (versioned) |
| Token endpoint | graph.facebook.com/oauth/access_token |
graph.facebook.com/v19.0/oauth/access_token |
| Token response | URL-encoded access_token=...&expires=... |
JSON { "access_token", "expires_in" } |
| Profile call | GET /me (full profile) |
GET /me?fields=id,name,email (fields required) |
| Returned with basic login | Needs email permission + App Review |
|
| Friends | read_friendlists → all friends |
user_friends → only friends who use your app |
| Pages | manage_pages |
granular pages_show_list, pages_read_engagement, ... |
| Groups | user_groups |
removed; Groups API needs the app installed by an admin |
| Query language | FQL / FBML | removed — use Graph API fields |
| SDK | PHP SDK / old JS SDK | versioned JS SDK or server-side requests |
What can you actually access from Facebook today?
Be realistic about scope before you build. Facebook grants public_profile automatically, but almost everything else needs App Review and often Business Verification, with a documented use case. There are two access tiers: Standard Access (only people with a role on the app — admins, developers, testers — can log in) and Advanced Access (the general public), which is what App Review unlocks.
| Permission | Grants | Access level needed |
|---|---|---|
public_profile |
id, name, first/last name, profile picture |
Granted by default with Login |
email |
The user's email address | Advanced Access via App Review |
user_friends |
Friends who also use your app (not all friends) | App Review |
pages_show_list, pages_read_engagement |
Pages the user manages | App Review + Business Verification |
user_posts, user_photos |
The user's own posts/photos | App Review + Business Verification |
During development you can test email and other scopes with app-role users without review — but you must pass App Review before the public sees them.
How do you create a Facebook (Meta) app?
- Go to Meta for Developers (
developers.facebook.com), open My Apps → Create App, and pick a use case (for login, choose Authenticate and request data from users with Facebook Login). - From the dashboard, add the Facebook Login product.
- Under Facebook Login → Settings, add your exact Valid OAuth Redirect URIs (for example
https://yourapp.com/auth/facebook/callback/). They must match byte-for-byte. - Copy your App ID and App Secret from App Settings → Basic, and set a Data Deletion Request callback or URL (required to go live).
- Load the App ID and secret from environment variables — never commit them.
# settings.py
import os
FACEBOOK_APP_ID = os.environ["FACEBOOK_APP_ID"]
FACEBOOK_APP_SECRET = os.environ["FACEBOOK_APP_SECRET"]
FACEBOOK_REDIRECT_URI = "https://yourapp.com/auth/facebook/callback/"
FACEBOOK_API_VERSION = "v19.0" # pin a version; old versions are sunset on a rolling basisHow do you build the OAuth 2.0 login redirect in Django?
For a server-side flow, send the user to the versioned login dialog with your scopes and a random state value (stored in the session) to protect against CSRF.
# views.py
import secrets
from urllib.parse import urlencode
from django.conf import settings
from django.shortcuts import redirect
def facebook_login(request):
state = secrets.token_urlsafe(16)
request.session["facebook_state"] = state
params = {
"client_id": settings.FACEBOOK_APP_ID,
"redirect_uri": settings.FACEBOOK_REDIRECT_URI,
"state": state,
"response_type": "code",
"scope": "public_profile,email", # email needs App Review for public use
}
base = f"https://www.facebook.com/{settings.FACEBOOK_API_VERSION}/dialog/oauth"
return redirect(f"{base}?{urlencode(params)}")How do you exchange the code for an access token?
Facebook redirects back to your callback with code and state (or ?error=access_denied if the user cancels). Verify the state, then call the token endpoint. Unlike the 2015 API, the response is JSON, not a URL-encoded query string.
# views.py (continued)
import requests
from django.conf import settings
from django.http import HttpResponseBadRequest
def facebook_callback(request):
if request.GET.get("state") != request.session.pop("facebook_state", None):
return HttpResponseBadRequest("Invalid OAuth state")
if request.GET.get("error"):
# e.g. ?error=access_denied when the user clicks Cancel
return HttpResponseBadRequest("Authorization was not granted")
code = request.GET.get("code")
version = settings.FACEBOOK_API_VERSION
token_res = requests.get(
f"https://graph.facebook.com/{version}/oauth/access_token",
params={
"client_id": settings.FACEBOOK_APP_ID,
"client_secret": settings.FACEBOOK_APP_SECRET,
"redirect_uri": settings.FACEBOOK_REDIRECT_URI,
"code": code,
},
timeout=10,
)
token_res.raise_for_status()
access_token = token_res.json()["access_token"] # also returns expires_in
return _fetch_profile(request, access_token)How do you fetch the user with the Graph API?
Call /me on the versioned Graph API and ask for the exact fields you need — a bare /me no longer returns the whole profile. Reading email requires the email permission to have been granted and approved. For server-to-server calls you should also send appsecret_proof (an HMAC-SHA256 of the token keyed with your app secret) to harden the request.
# views.py (continued)
import hashlib
import hmac
def _appsecret_proof(access_token):
return hmac.new(
settings.FACEBOOK_APP_SECRET.encode(),
access_token.encode(),
hashlib.sha256,
).hexdigest()
def _fetch_profile(request, access_token):
version = settings.FACEBOOK_API_VERSION
res = requests.get(
f"https://graph.facebook.com/{version}/me",
params={
"fields": "id,name,email,picture",
"access_token": access_token,
"appsecret_proof": _appsecret_proof(access_token),
},
timeout=10,
)
res.raise_for_status()
data = res.json()
facebook_id = data["id"] # app-scoped id, stable per app
full_name = data.get("name")
email = data.get("email") # present only if email was granted
# ... create/update your Django User here, then log them in.
return redirect("dashboard")Should you use the JavaScript SDK or a server-side redirect?
Both start the same OAuth 2.0 flow; they differ in where the token ends up. The JS SDK (FB.login) is quick for client-side login and social plugins, but the access token lives in the browser. The server-side redirect keeps your client_secret and tokens on the backend, which is what you want for creating accounts, sessions, or posting.
| Aspect | JavaScript SDK (FB.login) |
Server-side OAuth redirect |
|---|---|---|
| Where the token lives | Browser (short-lived) | Your server |
client_secret exposure |
Never sent to the client | Stays server-side |
| Best for | Quick login, social plugins | Account creation, sessions, posting |
| Code → token exchange | SDK handles it | You POST code → token |
| Long-lived token | Exchange on the server anyway | Exchange on the server |
If you do use the JS SDK, initialise it with a pinned version and request scopes in FB.login:
// Facebook JavaScript SDK (load async, then init)
window.fbAsyncInit = function () {
FB.init({
appId: 'YOUR_APP_ID',
cookie: true,
xfbml: false,
version: 'v19.0', // pin the Graph API version
});
};
function loginWithFacebook() {
FB.login(
function (response) {
if (response.authResponse) {
// Send response.authResponse.accessToken to your Django backend,
// verify it server-side, then create the user session.
console.log('Token:', response.authResponse.accessToken);
} else {
console.log('User cancelled or did not authorize.');
}
},
{ scope: 'public_profile,email' }
);
}How do you debug tokens and get a long-lived token?
The token from the login flow is short-lived (about 1–2 hours from the JS SDK, ~60 days from the server flow). Use the Access Token Debugger or the debug_token endpoint to inspect a token's scopes, expiry, and the app it belongs to, and exchange a short-lived token for a long-lived one (~60 days) on the server with grant_type=fb_exchange_token.
# tokens.py
import requests
from django.conf import settings
GRAPH = f"https://graph.facebook.com/{settings.FACEBOOK_API_VERSION}"
APP_TOKEN = f"{settings.FACEBOOK_APP_ID}|{settings.FACEBOOK_APP_SECRET}"
def exchange_for_long_lived(short_lived_token):
res = requests.get(
f"{GRAPH}/oauth/access_token",
params={
"grant_type": "fb_exchange_token",
"client_id": settings.FACEBOOK_APP_ID,
"client_secret": settings.FACEBOOK_APP_SECRET,
"fb_exchange_token": short_lived_token,
},
timeout=10,
)
res.raise_for_status()
return res.json()["access_token"] # valid ~60 days
def debug_token(token):
res = requests.get(
"https://graph.facebook.com/debug_token",
params={"input_token": token, "access_token": APP_TOKEN},
timeout=10,
)
res.raise_for_status()
return res.json()["data"] # scopes, expires_at, is_valid, app_idShould you use django-allauth instead of hand-rolling this?
For production sign-in, yes. django-allauth ships a facebook provider that runs the whole OAuth 2.0 handshake, verifies tokens, optionally uses the JS SDK, and wires Facebook into Django's auth system — so you do not maintain the callback, state, appsecret_proof, and token-exchange code yourself. Build the manual requests flow above when you need fine-grained control; reach for allauth when you just need "Log in with Facebook."
# settings.py
INSTALLED_APPS = [
# ...
"django.contrib.sites",
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.facebook",
]
SITE_ID = 1
SOCIALACCOUNT_PROVIDERS = {
"facebook": {
"METHOD": "oauth2", # or "js_sdk"
"SCOPE": ["public_profile", "email"],
"FIELDS": ["id", "name", "email"],
"VERSION": "v19.0",
# Configure the App ID/Secret via a SocialApp in the Django admin.
}
}Which Facebook permissions and endpoints do you actually need?
| Goal | Endpoint | Permission(s) | App Review? |
|---|---|---|---|
| Basic login + profile | GET /me?fields=id,name,picture |
public_profile |
No |
| Capture verified email | GET /me?fields=email |
email |
Yes (Advanced Access) |
| List friends using your app | GET /me/friends |
user_friends |
Yes |
| List managed Pages | GET /me/accounts |
pages_show_list |
Yes + Business Verification |
| Inspect or refresh a token | GET /debug_token, fb_exchange_token |
App access token | No |
This same OAuth 2.0 pattern powers most social integrations. If you are wiring up several providers, see our companion guides on single sign-on with Auth0 in Django, integrating the LinkedIn API in Python Django, and Google sign-in with Django.
Frequently Asked Questions
Is the old Facebook PHP SDK still usable in 2026?
No. The legacy PHP SDK, FQL, and FBML are all retired, and unversioned graph.facebook.com calls fail. Use the versioned Graph API (for example v19.0) with the JavaScript SDK or a server-side HTTP client like Python's requests, or use django-allauth.
Do I need App Review to get a user's email from Facebook?
Yes, to read it for the general public. public_profile is granted automatically, but the email permission needs Advanced Access via App Review. During development you can test it with users who have a role on the app (admins, developers, testers) without review.
Which Graph API version should I use, and will it break?
Pin an explicit version such as v19.0 in every call and in the JS SDK FB.init. Facebook sunsets each version on a rolling schedule (roughly two years), so calls without a version, or on a retired version, will eventually fail — plan to bump the version periodically.
Why is the friends list almost empty?
By design. The user_friends permission only returns the user's friends who also use your app and have granted the same permission — not their entire friends list. The old read_friendlists scope that returned all friends was removed.
What is the data deletion callback, and is it mandatory?
Meta requires every app that stores user data to provide a Data Deletion Request URL or callback so users can ask you to delete their data. You configure it in the app dashboard, and it is checked during App Review before you can go live.
Should I build the OAuth flow manually or use django-allauth?
Use django-allauth's facebook provider for standard "Log in with Facebook" — it manages the dialog, callback, state, token exchange, and appsecret_proof. Build the manual requests flow when you need custom control, such as bespoke posting logic or storing tokens in your own model.