# How Clerk implements OAuth

Clerk supports secure, standards-compliant OAuth flows for your applications. You can create OAuth apps, enable dynamic client registration, manage consent screens, and configure public clients. This guide covers all key OAuth features and configurations in Clerk and helps you set up the right OAuth flow for your needs.

## Build an OAuth app with Clerk

To create or edit an OAuth app, navigate to the [**OAuth applications**](https://dashboard.clerk.com/~/oauth-applications) page in the Clerk Dashboard. The following sections explain the available settings for configuring your OAuth app.

### Dynamic client registration

In addition to configuring OAuth apps manually through the [Clerk Dashboard](https://dashboard.clerk.com/~/oauth-applications), Clerk supports [dynamic client registration](https://datatracker.ietf.org/doc/html/rfc7591), allowing OAuth clients to be created programmatically via a public API endpoint. To enable this feature, navigate to the [**OAuth applications**](https://dashboard.clerk.com/~/oauth-applications) page in the Clerk Dashboard and enable **Dynamic client registration**.

> Dynamic client registration creates a public, unauthenticated endpoint that anyone can use to register OAuth clients with your authorization service. While this can enable use cases like multi-tenant SaaS platforms, developer marketplaces, and some MCP integrations, it also introduces significant security risks:
>
> - Attackers can create clients anonymously without audit trails.
> - Malicious clients can use legitimate-sounding names to trick users.
> - Administrative overhead increases substantially for monitoring and cleanup.
>
> In some use cases, dynamic client registration can be a requirement. However, it should only be enabled after carefully evaluating and accepting the associated security risks.
>
> Additionally, when dynamic client registration is enabled, the OAuth consent screen is automatically enforced and cannot be disabled - this prevents dangerous combinations that could lead to CSRF vulnerabilities.

### Scopes

Scopes define the level of access and specific user data that will be shared with the client app during authentication. The following scopes are currently available:

| Scope              | Access                                                                                                                                                                                                                                           |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `profile`          | Grant access to the user's personal information, such as first and last name, avatar, and username                                                                                                                                               |
| `email`            | Grant access to the user's email address                                                                                                                                                                                                         |
| `public_metadata`  | Grant access to the user's public and unsafe metadata                                                                                                                                                                                            |
| `private_metadata` | Grant access to the user's private metadata                                                                                                                                                                                                      |
| `openid`           | Enables the OpenID Connect flow                                                                                                                                                                                                                  |
| `user:org:read`    | Grant access to the user's Organization information. Only available when the instance has [Organizations](https://clerk.com/docs/guides/organizations/overview.md) enabled. See [Organizations and OAuth](#organizations-and-oauth) for details. |

> Support for adding custom OAuth scopes is **not yet available**, but development is underway. The goal is to allow custom scopes to be added, accepted, and checked through Clerk SDKs. An update will be provided when this feature is available.
>
> For early access to custom OAuth scopes, please vote or provide feedback on the [roadmap here](https://feedback.clerk.com/roadmap?id=d2d88be9-4d4f-45e6-997e-61d0b2a34bc9).

### Public clients and PKCE

In OAuth flows where a private server environment is available, the server can store and use a `client_secret` to securely exchange an authorization code for an access token. However, in mobile apps or single-page apps (SPAs), there is often no private server-side environment available to store the `client_secret` and run this exchange. These are called **public clients** because they cannot securely store secrets. Any secret you embed in a mobile app can be extracted by someone with the right tools, and anything in a browser-based app is visible to anyone who opens the developer tools, as well as browser extensions, etc.

To support these use cases, Clerk's OAuth implementation allows public clients to exchange an authorization code **without requiring a `client_secret`**. This makes it easier for mobile and browser-based apps to complete the token exchange and retrieve access and refresh tokens, without the risk of exposing long-lived secrets.

However, this process means **public clients** often need an additional layer of security to protect against interception attacks. This is where **[PKCE (Proof Key for Code Exchange)](https://datatracker.ietf.org/doc/html/rfc7636)** comes in - PKCE helps secure public clients by replacing the `client_secret` with a dynamically generated proof that **only the client who started the OAuth flow can provide**.

Here's how it works: instead of relying on a pre-shared secret, the client generates a random value called a **code verifier** at the start of each OAuth flow, along with a **code challenge**, which is a hashed version of the code verifier. The client sends the code challenge when starting the authorization flow, and later, during the token exchange, sends the original code verifier to prove it initiated the flow. The authorization service can then hash the code verifier to ensure it matches the code challenge.

This ensures that even if an attacker intercepts the authorization code, they can't complete the token exchange without the original code verifier. Only the client that started the flow has that value.

> [!QUIZ]
> Couldn't an attacker just steal the `code_verifier` from the browser (like from sessionStorage)?
>
> ***
>
> The key difference is _timing_ and _access_.
>
> The `code_verifier` only exists for the brief duration of the OAuth flow and is only accessible to the specific app that created it. A `client_secret`, on the other hand, would be permanently embedded in the app code where it could be extracted by anyone.
>
> Additionally, each PKCE flow uses a unique `code_verifier`, so even if one was somehow compromised, it couldn't be reused for other users or future flows.

To enable PKCE for a public client, you can enable the **Public** option for each OAuth app [in the Clerk Dashboard](https://dashboard.clerk.com/~/oauth-applications).

### Consent screen management

The OAuth consent screen shows users a confirmation dialog before they authorize OAuth applications. It ensures users understand _exactly_ what permissions they're granting before completing the OAuth flow.

**The consent screen displays:**

- The requesting app's name and logo.
- The name and logo of the app receiving the request.
- The specific access scopes that are being requested, in user-friendly language.
- Clear accept/deny options.

![OAuth Consent Screen](https://clerk.com/docs/raw/_public/images/oauth/consent-screen.png){{ style: { maxWidth: '460px' } }}

The consent screen is enabled by default for all OAuth apps. You can enable or disable the consent screen for each OAuth app [in the Clerk Dashboard](https://dashboard.clerk.com/~/oauth-applications).

> Enabling the consent screen for all OAuth apps is **strongly recommended**. Without a consent screen, any logged-in user who visits an OAuth authorization URL automatically grants access to any requested scopes. The consent screen acts as a critical security checkpoint, preventing malicious apps from silently gaining access to user accounts.

### Organizations and OAuth {{ id: 'organizations-and-oauth' }}

When your instance has [Organizations](https://clerk.com/docs/guides/organizations/overview.md) enabled, the `user:org:read` scope becomes available for OAuth applications. This scope enables an integration between Organizations and the OAuth consent flow.

When an OAuth client requests the `user:org:read` scope, the OAuth consent screen displays an **Organization selector** that allows the user to choose which Organization to associate with the token. After the user selects an Organization and grants consent, an `org_id` claim is included in the access token and, when the `openid` scope is also requested, the ID token. The `org_id` property is also available at the user info and token introspection endpoints.

## Token expiration and management

- OAuth access tokens expire **after 1 day**.
- Refresh tokens **never expire**.
- Authorization codes expire **after 10 minutes**.
- OIDC `id_token`s expire **after 1 day**.

## Access token format

Clerk issues OAuth access tokens as [JSON Web Tokens (JWTs)](https://clerk.com/docs/guides/how-clerk-works/tokens-and-signatures.md#json-web-tokens-jwts) by default. JWTs are self-contained tokens that can be verified without making a network request to Clerk's servers.

If your use case requires instant token revocation, you can configure your OAuth application to issue opaque tokens instead. Unlike JWTs, opaque tokens require a network request to Clerk for verification, but can be revoked immediately.

To configure the token format for an OAuth application, navigate to the [**OAuth applications**](https://dashboard.clerk.com/~/oauth-applications) page in the Clerk Dashboard and select the "Settings" tab, then toggle the **Generate access tokens as JWTs** switch on or off depending on your needs.

For a detailed comparison of JWT and opaque token formats, including trade-offs and when to use each, see [Token formats](https://clerk.com/docs/guides/development/machine-auth/token-formats.md).

## Authorization server metadata

An important feature of modern OAuth 2.0 and OpenID Connect (OIDC) implementations is **authorization server metadata**. This is a standardized JSON document published by the authorization server that describes its configuration, supported features and endpoints. This metadata makes it easier for clients to automatically discover important endpoints and features without needing manual configuration.

You can easily retrieve this metadata by loading your Clerk Frontend API URL with `/.well-known/oauth-authorization-server` appended to it. For example:

- `https://verb-noun-00.clerk.accounts.dev/.well-known/oauth-authorization-server` for a development environment.
- `https://clerk.<INSERT_YOUR_APP_DOMAIN>.com/.well-known/oauth-authorization-server` for a production environment.

This endpoint exposes all relevant details in a standardized JSON format. A sample metadata document might look like this:

```json
{
  "issuer": "https://well-hagfish-71.clerk.accounts.dev",
  "authorization_endpoint": "https://well-hagfish-71.clerk.accounts.dev/oauth/authorize",
  "token_endpoint": "https://well-hagfish-71.clerk.accounts.dev/oauth/token",
  "jwks_uri": "https://well-hagfish-71.clerk.accounts.dev/.well-known/jwks.json",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
  "scopes_supported": [
    "openid",
    "profile",
    "email",
    "public_metadata",
    "private_metadata",
    "user:org:read"
  ],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "name", "org_id"],
  "service_documentation": "https://clerk.com/docs",
  "ui_locales_supported": ["en"],
  "op_tos_uri": "https://clerk.com/legal/standard-terms",
  "code_challenge_methods_supported": ["S256"]
}
```

> The `user:org:read` scope and `org_id` claim are only included in this metadata when the instance has Organizations enabled. See [Organizations and OAuth](#organizations-and-oauth) for details.

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
