You need: the auth capability composed, and a Meta developer account.
Time: about fifteen minutes.
Create a Meta app
Google and GitHub are the same shape with less ceremony. What lands at the end is a credential.
Open the Meta app dashboard. Create an app, choose the type that fits, and add the Facebook Login product.
One app covers every environment — you register a redirect URI per environment on it, which is easier than GitHub’s one-app-per-environment rule.
The exact redirect URI
<baseURL><basePath>/callback/facebookUnder Facebook Login → Settings → Valid OAuth Redirect URIs, register one per environment:
| Environment | Redirect URI |
|---|---|
| dev | http://localhost:8787/auth/callback/facebook |
| staging | https://staging.example.com/auth/callback/facebook |
| production | https://example.com/auth/callback/facebook |
Facebook requires HTTPS for anything that is not localhost.
Anything unlisted is blocked with URL blocked: this redirect failed because it’s not in the app’s allowed redirect URIs.
Feature-branch previews
A preview URL is a host Facebook has never seen, so sign-in there fails.
To test on a branch, add that deployment’s own callback to the allowed list and point that deployment’s origin at the same host. Or run against staging — magic link and one-time code have no redirect URI and work anywhere.
Scope
Pithy requests email. You do not configure scopes anywhere.
Mobile
You do not register a deep link with Facebook. It only ever redirects to your Worker’s callback, and the Worker redirects onward to the app’s own deep link — which has to be in your trusted origins:
auth({
trustedOrigins: ["myapp://", "https://app.example.com"],
}),Store the credentials
The app id and app secret travel as one typed JSON secret:
pithy secrets create auth-facebook-credentialsThen enable it, with no credential values in config:
auth({
facebook: { enabled: true },
}),Account linking, and why the email is trusted
One account per verified email. A Facebook sign-in whose address matches an existing user links into that account rather than creating a second one.
Pithy trusts Facebook’s email as verified, which is the same trust Google and Apple get — and it is worth explaining, because Facebook’s OAuth response does not say so.
Facebook confirms a user’s email before it will hand it back, and Pithy validates the access token against your app before reading the profile. So the address is genuinely the signed-in user’s, verified by Facebook.
The complication is that Better Auth’s OAuth response carries no verification claim for Facebook, and Facebook’s own API exposes no such field. Without asserting it, Facebook would treat every address as unverified — and every sign-in would be refused the way an unverified GitHub address is.
So verification is asserted for Facebook’s own email only. That is a narrow, deliberate exception rather than a general relaxation.
When the credential will not read
An enabled provider whose secret is missing or malformed costs that provider and nothing else. A Facebook attempt answers 503 with auth/provider_unavailable, naming the provider — rather than the 404 a provider nobody enabled gets.