# Cloudflare (/docs/authentication/cloudflare)

Cloudflare provider setup and usage.



<Steps>
  <Step>
    ### Get your Cloudflare credentials [#get-your-cloudflare-credentials]

    To use Cloudflare as a social provider, create an OAuth client from the [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/oauth-clients).

    1. Select your account in the Cloudflare dashboard
    2. Go to **Manage Account** > **OAuth clients**
    3. Select **Create client**
    4. Use the Authorization Code flow with `code` as the response type
    5. Set **Token Authentication Method** to `client_secret_basic`
    6. Set the redirect URL to `http://localhost:3000/api/auth/callback/cloudflare` for local development. For production, set it to your application URL, for example `https://example.com/api/auth/callback/cloudflare`
    7. Select **User Details Read** as a required scope, then select any other Cloudflare API scopes your application needs
    8. Save your client ID and client secret securely

    If you change the base path of the auth routes, update the redirect URL accordingly.
  </Step>

  <Step>
    ### Configure the provider [#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`).

    <Callout>
      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.
    </Callout>

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth"

    export const auth = betterAuth({
        socialProviders: {
            cloudflare: { // [!code highlight]
                clientId: process.env.CLOUDFLARE_CLIENT_ID as string, // [!code highlight]
                clientSecret: process.env.CLOUDFLARE_CLIENT_SECRET as string, // [!code highlight]
            }, // [!code highlight]
        },
    })
    ```
  </Step>
</Steps>

## Usage [#usage]

### Sign in with Cloudflare [#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`.

```ts
import { authClient } from "@/lib/auth-client"

const { data, error } = await authClient.signIn.social({
    provider: "cloudflare"
})
```

## Options [#options]

For the full list of options supported by all social providers, check the [Provider Options](/docs/concepts/oauth#provider-options).

### Scopes [#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:

```ts title="auth.ts"
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: [ // [!code highlight]
                "workers-platform.read", // [!code highlight]
            ], // [!code highlight]
        },
    },
})
```

<Callout type="warn">
  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`.
</Callout>

### Token authentication method [#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.

```ts title="auth.ts"
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 // [!code highlight]
        },
    },
})
```

If your Cloudflare OAuth client is configured with `client_secret_post`, set `tokenEndpointAuthMethod` to `client_secret_post`:

```ts title="auth.ts"
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", // [!code highlight]
        },
    },
})
```

### Clients without a secret [#clients-without-a-secret]

Cloudflare's [OAuth flow guidance](https://developers.cloudflare.com/fundamentals/oauth/create-an-oauth-client/#choose-a-flow) 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.

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    socialProviders: {
        cloudflare: {
            clientId: process.env.CLOUDFLARE_CLIENT_ID as string,
            tokenEndpointAuthMethod: "none", // [!code highlight]
        },
    },
})
```

### Client visibility [#client-visibility]

Cloudflare [client visibility](https://developers.cloudflare.com/fundamentals/oauth/create-an-oauth-client/#private-and-public-clients) 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 [#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:

```ts title="auth.ts"
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"], // [!code highlight]
        },
    },
})
```

Keep `offline_access` opt-in when Cloudflare access is only needed during sign-in.

### Profile and email verification [#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`:

```ts title="auth.ts"
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 }
            },
        },
    },
})
```

<Callout type="warn">
  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.
</Callout>

For more information about Cloudflare OAuth, refer to the [Cloudflare OAuth documentation](https://developers.cloudflare.com/fundamentals/oauth/).

