You implement single sign on with Facebook by registering a Meta app, adding the Facebook Login product, whitelisting your exact redirect URIs, and then running either the JavaScript SDK flow in the browser or the manual OAuth flow with a server side code exchange. Whichever you pick, the rule that matters is the same: never trust an access token that arrives from the client until your server has verified it against Meta’s debug_token endpoint. Everything else is plumbing.
https://www.facebook.com/v25.0/dialog/oauth with client_id, redirect_uri, state and scope. Exchange the returned code at https://graph.facebook.com/v25.0/oauth/access_token using your app secret, then validate the token with debug_token and check that app_id matches your app. Add a Data Deletion Request URL and submit for App Review before you ask for anything beyond public_profile and email.The walkthrough below covers app setup, both flows, token verification, the long lived token exchange, the data deletion callback Meta requires, and the App Review gates that block a launch if you leave them until the end.
Set up the app and the redirect URIs
Create the app in the Meta App Dashboard, then add the Facebook Login product. Two settings do most of the work.
Valid OAuth Redirect URIs. Under Facebook Login > Settings, list every callback URL your app will use, exactly. Meta does a strict comparison including scheme, host, port and path. https://app.example.com/auth/facebook/callback and https://app.example.com/auth/facebook/callback/ are different values. Add your local development URL too, or logins will fail on your machine only.
App Domains and the site URL. Under Settings > Basic, set App Domains to the registered domain and add a Website platform with the site URL. The JavaScript SDK refuses to run on an origin that is not covered here.
Keep the app secret out of client code, out of your repository and out of mobile binaries. It belongs in a server environment variable. If a secret leaks, rotate it in Settings > Basic immediately.
Choose a flow
| Flow | Best for | Where the token lands | Main tradeoff |
|---|---|---|---|
| JavaScript SDK | Single page apps, quick integrations | Browser, then posted to your server | Loads Meta script on every page, still needs server verification |
| Manual OAuth (code flow) | Server rendered apps, anything with a backend session | Server only | More code, one extra redirect |
| Native mobile SDK | iOS and Android apps | Device, then posted to your server | Platform specific setup and key hashes |
If your app already has a session layer, take the manual code flow. It keeps the token on the server, works without JavaScript, and gives you one obvious place to attach your own user record. Reach for the SDK when you want a login button in a page that has no backend routing to spare. For the mobile side, see our companion piece on using Facebook SDKs for mobile app development.
The JavaScript SDK flow
Load the SDK, initialize it with your app ID and a pinned Graph API version, then call FB.login from a real user click.
window.fbAsyncInit = function () {
FB.init({
appId: '1234567890',
cookie: true,
xfbml: false,
version: 'v25.0'
});
};
document.querySelector('#fb-login').addEventListener('click', function () {
FB.login(function (response) {
if (response.status === 'connected') {
// Send the token to your server. Do not trust it here.
fetch('/auth/facebook/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.authResponse.accessToken })
});
}
}, { scope: 'public_profile,email' });
});FB.getLoginStatus tells you whether a returning visitor is already connected, so you can skip the button. The status is one of connected, not_authorized or unknown. Note that FB.logout can also log the person out of Facebook itself, which is rarely what you want, and that logging out does not revoke permissions. Revocation is a separate action the user takes in their Facebook settings, or that you trigger with a DELETE on the permissions edge.
The manual OAuth flow
Redirect the browser to the OAuth dialog with a state value you generated and stored in the session. That value is your CSRF defense, and skipping it is the single most common security bug in these integrations.
https://www.facebook.com/v25.0/dialog/oauth
?client_id=YOUR_APP_ID
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Ffacebook%2Fcallback
&state=RANDOM_SESSION_BOUND_VALUE
&scope=public_profile,email
&response_type=codeFacebook sends the user back to your callback with code and state. Compare the returned state to the stored one, reject on mismatch, then exchange the code from your server.
curl -G "https://graph.facebook.com/v25.0/oauth/access_token" \
--data-urlencode "client_id=YOUR_APP_ID" \
--data-urlencode "client_secret=YOUR_APP_SECRET" \
--data-urlencode "redirect_uri=https://app.example.com/auth/facebook/callback" \
--data-urlencode "code=THE_CODE_FROM_THE_CALLBACK"The response is JSON with access_token, token_type and expires_in. The redirect_uri you send here must match the one used in the dialog exactly, byte for byte, or the exchange fails with a confusing mismatch error.
Verify the token before you create a session
A token posted from a browser could have been minted for a different app. Meta’s debug_token endpoint is how you prove otherwise. Call it with an app access token, then assert three things: is_valid is true, app_id equals your app ID, and the scopes include what you require.
curl -G "https://graph.facebook.com/debug_token" \
--data-urlencode "input_token=USER_TOKEN" \
--data-urlencode "access_token=YOUR_APP_ID|YOUR_APP_SECRET"
# Response shape
# {"data":{"app_id":"1234567890","type":"USER","application":"Your App",
# "expires_at":1770000000,"is_valid":true,"scopes":["public_profile","email"],
# "user_id":"987654321"}}Only after that check should you read the profile and create your own session.
curl -G "https://graph.facebook.com/v25.0/me" \
--data-urlencode "fields=id,name,email" \
--data-urlencode "access_token=USER_TOKEN"Store the returned id as the account link, not the email, and keep the same discipline if you later add Page or ads endpoints, as described in retrieving insights and analytics data from a Facebook Page. The ID is app scoped and stable for your app. The email is optional, can be missing when the person signed up with a phone number, and can change.
appsecret_proof with server calls. It is an HMAC SHA256 of the access token keyed with your app secret, and it stops a stolen token from being used outside your servers.If you need the token to survive past the short default lifetime, exchange it for a long lived one, which generally lasts about sixty days. Use grant_type=fb_exchange_token with your app ID, app secret and the short lived token, server side only, and replace the stored copy with the new string.
Data deletion callback and App Review
Meta requires a way for people to delete the data your app holds about them. In the App Dashboard settings you supply a Data Deletion Request URL over HTTPS. Meta posts a signed request containing an app scoped user_id. Your endpoint starts the deletion and responds with JSON containing a status URL and a confirmation code:
{ "url": "https://app.example.com/deletion?id=abc123",
"confirmation_code": "abc123" }That status URL has to show a human readable explanation of where the request stands, including a justification if you are declining to delete something for a legal reason.
App Review is the other gate. public_profile and email are granted automatically, so a plain sign on button works in development without review. Anything further, Pages data, Instagram data, ads permissions, needs a submission with a screencast, a test user and a written use case. Build the review submission into your timeline rather than discovering it the week you plan to launch. The same review process governs the API work described in handling Facebook API authentication.
Troubleshooting
“URL blocked: this redirect failed.” The redirect_uri is not in Valid OAuth Redirect URIs, or it differs by a trailing slash, a port or a query string. Paste the exact value from the failing request into the allowlist.
The email field comes back empty. The person either denied the email permission or has no confirmed email on the account. Handle it: ask for an email in your own signup step rather than failing the login.
“Can’t load URL: the domain of this URL isn’t included in the app’s domains.” Add the registered domain under Settings > Basic > App Domains and make sure a Website platform is present.
Login works for you but nobody else. The app is still in development mode. Only developers, testers and admins can log in. Switch the app to live, which requires a privacy policy URL and a completed data deletion setup.
Tokens stop working after about an hour. That is the short lived token expiring. Exchange it for a long lived token server side, or re authenticate. Do not try to cache the short lived token past its expiry.
Frequently asked questions
Do I need App Review just to add a login button?
No. The public_profile and email permissions are granted without review, so a basic sign on works as soon as the app is live and has a privacy policy and data deletion route. Review is required only when you request additional permissions or access more than the basic profile.
Is the JavaScript SDK secure enough on its own?
Only if your server verifies the token. The SDK hands the browser an access token, and a browser is not a trustworthy source. Post the token to your backend, run debug_token, confirm the app_id matches your app, and create your own session from the verified user ID.
Should I store the Facebook access token?
Store it only if you make Graph API calls on the user’s behalf later. Encrypt it at rest, keep it out of logs, and refresh it before the roughly sixty day long lived window closes. If all you need is authentication, discard the token once you have created your session.
What happens when a user deletes their Facebook account?
Your stored token stops working and Graph calls start failing with an invalid token error. Handle that gracefully by prompting for another login method. Also make sure your data deletion endpoint works, because Meta may send a deletion request for that person.
Can I use the same app for web and mobile?
Yes. One Meta app can carry a Website platform plus iOS and Android platforms, each with its own bundle ID or package name and key hash. Keep the redirect URIs and app domains accurate for every platform, since a mistake in one breaks only that surface.
The bottom line
Facebook Login is a standard OAuth 2.0 flow with a few Meta specific requirements bolted on: exact redirect URI matching, an app secret that never leaves the server, a token verification step you cannot skip, and a data deletion callback before the app can go live.
Build the manual code flow if you have a backend, verify every token with debug_token, key your user records on the app scoped ID, and pin an explicit Graph API version. Do that and the integration keeps working through Meta’s next round of dashboard renames.
