Cloudflare
Cloudflare provider setup and usage.
Get your Cloudflare credentials
To use Cloudflare as a social provider, create an OAuth client from the Cloudflare dashboard.
- Select your account in the Cloudflare dashboard
- Go to Manage Account > OAuth clients
- Select Create client
- Use the Authorization Code flow with
codeas the response type - Set Token Authentication Method to
client_secret_basic - Set the redirect URL to
http://localhost:3000/api/auth/callback/cloudflarefor local development. For production, set it to your application URL, for examplehttps://example.com/api/auth/callback/cloudflare - Select User Details Read as a required scope, then select any other Cloudflare API scopes your application needs
- Save your client ID and client secret securely
If you change the base path of the auth routes, update the redirect URL accordingly.
Configure the provider
To configure the provider, pass the clientId and clientSecret to socialProviders.cloudflare in your auth configuration. The provider requests user-details.read by default so Better Auth can read the user's profile details from the Cloudflare API /user endpoint (https://api.cloudflare.com/client/v4/user).
Cloudflare's OIDC userinfo endpoint only returns the sub claim, so the provider reads the user's email and name from the Cloudflare API /user endpoint instead. This requires the user-details.read scope to be granted on your OAuth client.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string,
},
},
})Usage
Sign in with Cloudflare
To sign in with Cloudflare, use the signIn.social function provided by the client. The provider should be set to cloudflare.
import { authClient } from "@/lib/auth-client"
const { data, error } = await authClient.signIn.social({
provider: "cloudflare"
})Options
For the full list of options supported by all social providers, check the Provider Options.
Scopes
Cloudflare OAuth scope names correspond to Cloudflare API token permission names. Select the scopes your application needs when creating the OAuth client. Better Auth does not limit which Cloudflare scopes you can request; pass any scope IDs that are configured on your Cloudflare OAuth client.
The provider requests user-details.read by default. Configure it as a required scope on the Cloudflare OAuth client, because Better Auth needs it to read the user's email from the /user endpoint. If it is optional, a user can decline it and sign-in cannot complete.
To request additional Cloudflare API scopes, select them on your Cloudflare OAuth client and add their exact scope IDs with the scope option.
For example:
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string,
scope: [
"workers-platform.read",
],
},
},
})The authorization request must not include scopes that are missing from the Cloudflare OAuth client configuration. Cloudflare's docs say exact OAuth scope IDs are available from GET https://api.cloudflare.com/client/v4/oauth/scopes.
Token authentication method
Use client_secret_basic for regular server-side Better Auth applications. This is the default used by the Cloudflare provider when clientSecret is configured.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string,
// tokenEndpointAuthMethod: "client_secret_basic", // default
},
},
})If your Cloudflare OAuth client is configured with client_secret_post, set tokenEndpointAuthMethod to client_secret_post:
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string,
tokenEndpointAuthMethod: "client_secret_post",
},
},
})Clients without a secret
Cloudflare's OAuth flow guidance requires clients that cannot securely store a secret, such as browser-based, mobile, desktop, or CLI applications, to use the Authorization Code flow with PKCE (S256) and token_endpoint_auth_method set to none. For one of these clients, omit clientSecret and set tokenEndpointAuthMethod to none. Better Auth supplies the PKCE challenge and verifier during the authorization flow.
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
tokenEndpointAuthMethod: "none",
},
},
})Client visibility
Cloudflare client visibility is separate from token endpoint authentication. New OAuth clients are private, so only members of the parent Cloudflare account can authorize them. To allow any Cloudflare user to authorize your application, promote the client to public.
Promotion requires a client name, logo, client URL, scopes, and DNS TXT verification for the client domain. It is permanent. Public visibility does not determine whether the client uses a secret; choose the token authentication method based on whether the application can securely store one.
Refresh tokens
The default configuration is sufficient for signing users in. If your application needs long-lived access to the Cloudflare API, configure the OAuth client with both the authorization_code and refresh_token grant types, then request the offline_access scope explicitly:
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string,
scope: ["offline_access"],
},
},
})Keep offline_access opt-in when Cloudflare access is only needed during sign-in.
Profile and email verification
The provider reads the user's profile from the Cloudflare API /user endpoint. This returns the user's id and email, with optional first_name and last_name fields. The display name is composed from the available name fields and falls back to the email when both are absent. Cloudflare does not provide a profile picture, so image is left empty.
Cloudflare's /user endpoint does not expose an email-verification status, so the account email is treated as unverified (emailVerified: false) by default. If your application verifies the email through another trusted mechanism, return that result from mapProfileToUser:
import { betterAuth } from "better-auth"
import { verifyEmailOwnership } from "@/lib/email-verification"
export const auth = betterAuth({
socialProviders: {
cloudflare: {
clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string,
mapProfileToUser: async (profile) => {
// Verify the user's email through a trusted mechanism.
const emailVerified = await verifyEmailOwnership(profile.email)
return { emailVerified }
},
},
},
})Reading the profile requires the user-details.read scope. If it is not granted on your Cloudflare OAuth client, the /user request fails and sign-in cannot complete.
For more information about Cloudflare OAuth, refer to the Cloudflare OAuth documentation.