Facebook Login and Graph API Integration in Django

Blog / Django · April 2, 2015 · Updated June 10, 2026 · 9 min read
Facebook Login and Graph API Integration in Django

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_profile is 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 email permission 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_friends returns only friends who also use your app, manage_pages was split into granular pages_* permissions, and user_groups was 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 facebook provider for production sign-in instead of hand-rolling the handshake.
  • Everything legacy is dead: the PHP SDK, FQL, FBML, and unversioned graph.facebook.com calls. Use requests on 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)
Email 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?

  1. 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).
  2. From the dashboard, add the Facebook Login product.
  3. 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.
  4. Copy your App ID and App Secret from App Settings → Basic, and set a Data Deletion Request callback or URL (required to go live).
  5. 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 basis

How 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_id

Should 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.

Share this article