From 824e683dd7ff57b44255b6e45ad7f19f321f0122 Mon Sep 17 00:00:00 2001 From: Jeff Escalante Date: Mon, 1 Jun 2026 18:25:18 -0500 Subject: [PATCH] Add custom OAuth consent security guide --- .../oauth-consent-custom-flow-examples.mdx | 444 ++++++++++++++++++ .../components/oauth-consent-examples.mdx | 138 ++++++ .../oauth/custom-consent-page.mdx | 151 ++++++ .../oauth/how-clerk-implements-oauth.mdx | 2 + .../auth-strategies/oauth/scoped-access.mdx | 2 + docs/manifest.json | 4 + .../authentication/oauth-consent.mdx | 25 +- docs/reference/hooks/use-oauth-consent.mdx | 128 +++-- 8 files changed, 835 insertions(+), 59 deletions(-) create mode 100644 docs/_partials/components/oauth-consent-custom-flow-examples.mdx create mode 100644 docs/_partials/components/oauth-consent-examples.mdx create mode 100644 docs/guides/configure/auth-strategies/oauth/custom-consent-page.mdx diff --git a/docs/_partials/components/oauth-consent-custom-flow-examples.mdx b/docs/_partials/components/oauth-consent-custom-flow-examples.mdx new file mode 100644 index 0000000000..e27534389a --- /dev/null +++ b/docs/_partials/components/oauth-consent-custom-flow-examples.mdx @@ -0,0 +1,444 @@ + + + ```tsx {{ filename: 'app/oauth-consent/custom-consent-form.tsx' }} + 'use client' + + import { useClerk, useOAuthConsent } from '@clerk/nextjs' + import { useSearchParams } from 'next/navigation' + + function getRedirectHostname(redirectUri: string) { + try { + return new URL(redirectUri).hostname + } catch { + return '' + } + } + + export function CustomConsentForm() { + const clerk = useClerk() + const searchParams = useSearchParams() + + const clientId = searchParams.get('client_id') ?? '' + const redirectUri = searchParams.get('redirect_uri') ?? '' + const scope = searchParams.get('scope') ?? undefined + const redirectHostname = getRedirectHostname(redirectUri) + + const { data, isLoading, error } = useOAuthConsent({ + oauthClientId: clientId, + scope, + }) + + if (!clientId || !redirectUri) { + return

Missing OAuth consent parameters.

+ } + + if (isLoading) { + return

Loading consent request...

+ } + + if (error || !data) { + return

Unable to load consent request.

+ } + + return ( +
+

{data.oauthApplicationName} wants access to your account

+ + {data.oauthApplicationLogoUrl && ( + {`${data.oauthApplicationName} + )} + + {data.oauthApplicationUrl && ( +

+ Application URL: {data.oauthApplicationUrl} +

+ )} +

+ Client ID: {data.clientId} +

+ +

+ Review the requested permissions before continuing. After you allow or deny access, you will + be redirected to {redirectHostname}. +

+ +
+ View full redirect URL + {redirectUri} +
+ +
    + {data.scopes.map((scope) => ( +
  • {scope.description || scope.scope}
  • + ))} +
+ + {/* Forward the original OAuth parameters, except fields set by this form. */} + {Array.from(searchParams.entries()) + .filter(([key]) => key !== 'consented' && key !== 'organization_id') + .map(([key, value], index) => ( + + ))} + + + +
+ ) + } + ``` + + ```tsx {{ filename: 'app/oauth-consent/page.tsx' }} + import { Show } from '@clerk/nextjs' + import { Suspense } from 'react' + import { CustomConsentForm } from './custom-consent-form' + + export const metadata = { + referrer: 'strict-origin-when-cross-origin', + } + + export default function OAuthConsentPage() { + return ( + + Loading consent request...

}> + +
+
+ ) + } + ``` +
+ + + ```tsx {{ filename: 'src/OAuthConsentPage.tsx' }} + import { Show, useClerk, useOAuthConsent } from '@clerk/react' + + function getConsentSearchParams() { + if (typeof window === 'undefined') { + return new URLSearchParams() + } + + return new URLSearchParams(window.location.search) + } + + function getRedirectHostname(redirectUri: string) { + try { + return new URL(redirectUri).hostname + } catch { + return '' + } + } + + function CustomConsentForm() { + const clerk = useClerk() + const searchParams = getConsentSearchParams() + + const clientId = searchParams.get('client_id') ?? '' + const redirectUri = searchParams.get('redirect_uri') ?? '' + const scope = searchParams.get('scope') ?? undefined + const redirectHostname = getRedirectHostname(redirectUri) + + const { data, isLoading, error } = useOAuthConsent({ + oauthClientId: clientId, + scope, + }) + + if (!clientId || !redirectUri) { + return

Missing OAuth consent parameters.

+ } + + if (isLoading) { + return

Loading consent request...

+ } + + if (error || !data) { + return

Unable to load consent request.

+ } + + return ( +
+

{data.oauthApplicationName} wants access to your account

+ + {data.oauthApplicationLogoUrl && ( + {`${data.oauthApplicationName} + )} + + {data.oauthApplicationUrl && ( +

+ Application URL: {data.oauthApplicationUrl} +

+ )} +

+ Client ID: {data.clientId} +

+ +

+ Review the requested permissions before continuing. After you allow or deny access, you will + be redirected to {redirectHostname}. +

+ +
+ View full redirect URL + {redirectUri} +
+ +
    + {data.scopes.map((scope) => ( +
  • {scope.description || scope.scope}
  • + ))} +
+ + {/* Forward the original OAuth parameters, except fields set by this form. */} + {Array.from(searchParams.entries()) + .filter(([key]) => key !== 'consented' && key !== 'organization_id') + .map(([key, value], index) => ( + + ))} + + + +
+ ) + } + + export function OAuthConsentPage() { + return ( + + + + ) + } + ``` + + ```html {{ filename: 'index.html' }} + + ``` +
+ + + ```tsx {{ filename: 'app/routes/oauth-consent.tsx' }} + import type { Route } from './+types/oauth-consent' + import { Show, useClerk, useOAuthConsent } from '@clerk/react-router' + import { useLocation } from 'react-router' + + export const meta: Route.MetaFunction = () => [ + { + name: 'referrer', + content: 'strict-origin-when-cross-origin', + }, + ] + + function getRedirectHostname(redirectUri: string) { + try { + return new URL(redirectUri).hostname + } catch { + return '' + } + } + + function CustomConsentForm() { + const clerk = useClerk() + const { search } = useLocation() + const searchParams = new URLSearchParams(search) + + const clientId = searchParams.get('client_id') ?? '' + const redirectUri = searchParams.get('redirect_uri') ?? '' + const scope = searchParams.get('scope') ?? undefined + const redirectHostname = getRedirectHostname(redirectUri) + + const { data, isLoading, error } = useOAuthConsent({ + oauthClientId: clientId, + scope, + }) + + if (!clientId || !redirectUri) { + return

Missing OAuth consent parameters.

+ } + + if (isLoading) { + return

Loading consent request...

+ } + + if (error || !data) { + return

Unable to load consent request.

+ } + + return ( +
+

{data.oauthApplicationName} wants access to your account

+ + {data.oauthApplicationLogoUrl && ( + {`${data.oauthApplicationName} + )} + + {data.oauthApplicationUrl && ( +

+ Application URL: {data.oauthApplicationUrl} +

+ )} +

+ Client ID: {data.clientId} +

+ +

+ Review the requested permissions before continuing. After you allow or deny access, you will + be redirected to {redirectHostname}. +

+ +
+ View full redirect URL + {redirectUri} +
+ +
    + {data.scopes.map((scope) => ( +
  • {scope.description || scope.scope}
  • + ))} +
+ + {/* Forward the original OAuth parameters, except fields set by this form. */} + {Array.from(searchParams.entries()) + .filter(([key]) => key !== 'consented' && key !== 'organization_id') + .map(([key, value], index) => ( + + ))} + + + +
+ ) + } + + export default function OAuthConsentPage() { + return ( + + + + ) + } + ``` +
+ + + ```tsx {{ filename: 'app/routes/oauth-consent.tsx' }} + import { Show, useClerk, useOAuthConsent } from '@clerk/tanstack-react-start' + import { createFileRoute, useLocation } from '@tanstack/react-router' + + export const Route = createFileRoute('/oauth-consent')({ + head: () => ({ + meta: [ + { + name: 'referrer', + content: 'strict-origin-when-cross-origin', + }, + ], + }), + component: OAuthConsentPage, + }) + + function getRedirectHostname(redirectUri: string) { + try { + return new URL(redirectUri).hostname + } catch { + return '' + } + } + + function CustomConsentForm() { + const clerk = useClerk() + const location = useLocation() + const searchParams = new URLSearchParams(location.searchStr) + + const clientId = searchParams.get('client_id') ?? '' + const redirectUri = searchParams.get('redirect_uri') ?? '' + const scope = searchParams.get('scope') ?? undefined + const redirectHostname = getRedirectHostname(redirectUri) + + const { data, isLoading, error } = useOAuthConsent({ + oauthClientId: clientId, + scope, + }) + + if (!clientId || !redirectUri) { + return

Missing OAuth consent parameters.

+ } + + if (isLoading) { + return

Loading consent request...

+ } + + if (error || !data) { + return

Unable to load consent request.

+ } + + return ( +
+

{data.oauthApplicationName} wants access to your account

+ + {data.oauthApplicationLogoUrl && ( + {`${data.oauthApplicationName} + )} + + {data.oauthApplicationUrl && ( +

+ Application URL: {data.oauthApplicationUrl} +

+ )} +

+ Client ID: {data.clientId} +

+ +

+ Review the requested permissions before continuing. After you allow or deny access, you will + be redirected to {redirectHostname}. +

+ +
+ View full redirect URL + {redirectUri} +
+ +
    + {data.scopes.map((scope) => ( +
  • {scope.description || scope.scope}
  • + ))} +
+ + {/* Forward the original OAuth parameters, except fields set by this form. */} + {Array.from(searchParams.entries()) + .filter(([key]) => key !== 'consented' && key !== 'organization_id') + .map(([key, value], index) => ( + + ))} + + + +
+ ) + } + + function OAuthConsentPage() { + return ( + + + + ) + } + ``` +
+
diff --git a/docs/_partials/components/oauth-consent-examples.mdx b/docs/_partials/components/oauth-consent-examples.mdx new file mode 100644 index 0000000000..da3098f4d3 --- /dev/null +++ b/docs/_partials/components/oauth-consent-examples.mdx @@ -0,0 +1,138 @@ + + + ```tsx {{ filename: 'app/oauth-consent/page.tsx' }} + import { OAuthConsent, Show } from '@clerk/nextjs' + + export const metadata = { + referrer: 'strict-origin-when-cross-origin', + } + + export default function OAuthConsentPage() { + return ( + + + + ) + } + ``` + + + + ```tsx {{ filename: 'src/OAuthConsentPage.tsx' }} + import { OAuthConsent, Show } from '@clerk/react' + + export function OAuthConsentPage() { + return ( + + + + ) + } + ``` + + ```html {{ filename: 'index.html' }} + + ``` + + + + ```tsx {{ filename: 'app/routes/oauth-consent.tsx' }} + import type { Route } from './+types/oauth-consent' + import { OAuthConsent, Show } from '@clerk/react-router' + + export const meta: Route.MetaFunction = () => [ + { + name: 'referrer', + content: 'strict-origin-when-cross-origin', + }, + ] + + export default function OAuthConsentPage() { + return ( + + + + ) + } + ``` + + + + ```tsx {{ filename: 'app/routes/oauth-consent.tsx' }} + import { OAuthConsent, Show } from '@clerk/tanstack-react-start' + import { createFileRoute } from '@tanstack/react-router' + + export const Route = createFileRoute('/oauth-consent')({ + head: () => ({ + meta: [ + { + name: 'referrer', + content: 'strict-origin-when-cross-origin', + }, + ], + }), + component: OAuthConsentPage, + }) + + function OAuthConsentPage() { + return ( + + + + ) + } + ``` + + + + ```astro {{ filename: 'src/pages/oauth-consent.astro' }} + --- + import { OAuthConsent, Show } from '@clerk/astro/components' + --- + + + + + + + + + ``` + + + + ```vue {{ filename: 'src/pages/OAuthConsentPage.vue' }} + + + + ``` + + ```html {{ filename: 'index.html' }} + + ``` + + + + ```vue {{ filename: 'pages/oauth-consent.vue' }} + + + + ``` + + diff --git a/docs/guides/configure/auth-strategies/oauth/custom-consent-page.mdx b/docs/guides/configure/auth-strategies/oauth/custom-consent-page.mdx new file mode 100644 index 0000000000..ecd1151513 --- /dev/null +++ b/docs/guides/configure/auth-strategies/oauth/custom-consent-page.mdx @@ -0,0 +1,151 @@ +--- +title: Customize the OAuth consent page +description: Learn how to configure a custom OAuth consent page, and how to avoid common consent phishing and token grant risks. +--- + +Clerk strongly recommends using the default OAuth consent page hosted by the [Account Portal](/docs/guides/account-portal/overview). OAuth consent is a security boundary: it is where a signed-in user decides whether an OAuth [client](!oauth-client) can receive [OAuth access tokens](!oauth-access-token) and access the requested data. A custom page can weaken that boundary if it hides the requesting application, misstates the requested scopes, buries the deny action, auto-approves access, or trains users to trust an unfamiliar consent surface. + +Only customize the OAuth consent page when you have a specific product requirement that the Account Portal cannot satisfy. If you customize it, the safest approach is to host a page on your own application domain and render Clerk's prebuilt [``](/docs/reference/components/authentication/oauth-consent) component. This keeps Clerk's consent logic, scope rendering, organization selection, form submission, and denial handling intact while letting you control the route, page styling, and surrounding page. + +> [!CAUTION] +> Consent phishing is a real OAuth attack pattern. Attackers can trick users into granting a malicious app access to their account data without stealing their password. Microsoft describes consent phishing as an attack where users grant permissions to malicious cloud applications, and notes that MFA or password resets do not remediate illicit consent grants because the user authorized the app itself. See Microsoft's guidance on [protecting against consent phishing](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/protect-against-consent-phishing) and [detecting illicit consent grants](https://learn.microsoft.com/en-us/defender-office-365/detect-and-remediate-illicit-consent-grants). + +After shipping a custom consent page, monitor [Application Logs](/docs/guides/dashboard/logs/application-logs) for OAuth grant activity. The `oauth_authorization.granted` event records successful user consent, and the `oauth_token.created` event records token issuance. Review these events for unfamiliar OAuth applications, unexpected users, or unusual grant volume. + +## Before you start + +Custom consent pages apply to Clerk's OAuth provider flows, where Clerk acts as the authorization service for OAuth applications that request access to user data through Clerk. + +> [!NOTE] +> If you're new to OAuth terminology, start with the [OAuth terminology guide](/docs/guides/configure/auth-strategies/oauth/overview#key-terminology). + +Before customizing the page: + +- Keep the consent screen enabled for every OAuth application. Without consent, a signed-in user who visits a valid authorization URL can grant access without seeing the request. +- Use narrow [scopes](/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth#scopes). OAuth security guidance recommends restricting access tokens to the minimum required privileges, and Google recommends choosing the most narrowly focused scopes possible. See [RFC 9700](https://www.rfc-editor.org/rfc/rfc9700) and Google's guide to [configuring OAuth consent and choosing scopes](https://developers.google.com/workspace/guides/configure-oauth-consent). +- Treat OAuth application branding as user-facing security information. App names, logos, client URLs, and redirect domains help users decide whether to grant access. +- Verify the route in development and production before enabling it for real users. A broken custom consent page can block OAuth authorization flows. + +## What the consent page must show + +A safe consent page must give users enough information to make an informed decision. At minimum, show: + +- The OAuth application's name. +- The OAuth application's logo, if one is available. +- The OAuth application's public URL, if one is available. +- The [resource service](!oauth-resource-service) receiving the access request. +- The signed-in user who is granting access. +- Every requested scope that requires consent, using clear descriptions. +- The redirect destination the user will return to after allowing or denying access. +- Equally visible **Allow** and **Deny** actions. + +The redirect destination presentation is security-sensitive. The prebuilt consent page shows a short domain derived from the `redirect_uri`, and lets the user expand it to view the full URL. This makes it harder for an attacker to hide the real root domain inside a very long URL with many subdomains or path segments. If you build your own UI, do not show only a truncated full URL. Show a clear domain summary, provide a way to view the full `redirect_uri`, and make sure the root domain cannot be pushed out of view by long subdomains. + +If you use the prebuilt component, do not hide the requested scopes, redirect warning, deny action, or application identity with `appearance` overrides. If you build a custom flow, include equivalent warning copy near the allow/deny controls. For example: "`` will be able to access `` and redirect you to ``. Review the requested permissions before continuing." + +## Recommended implementation + +Use Clerk's prebuilt [``](/docs/reference/components/authentication/oauth-consent) component on your custom consent route. The component reads the OAuth authorization parameters from the current URL, loads consent metadata for the signed-in user, renders the requested scopes, and submits the user's decision to Clerk. + +For example, create a route that renders ``: + + + +The [``](/docs/reference/components/control/show) component renders the consent screen only for signed-in users. In a normal OAuth authorization flow, Clerk redirects signed-out users to sign-in before sending them to the consent page. If users can visit your custom consent route directly, handle signed-out users the same way you handle any other protected route in your app. + +If your framework does not support route metadata, set the same referrer policy with a `` tag or equivalent framework API: + +```tsx + +``` + +The referrer policy is required because the consent form posts to Clerk's [Frontend API URL](!frontend-api-url). Without it, some cross-origin form submissions can send `Origin: null`, causing Clerk to reject the request. + +You can style `` with the standard [`appearance`](/docs/guides/customizing-clerk/appearance-prop/overview) prop, but keep the security content intact. The Account Portal consent page is built from Clerk's prebuilt consent component, so using `appearance` is the safer option when your goal is visual customization. Use a fully custom flow only when the prebuilt component cannot support your required layout or interaction. + +## Configure the custom route + +After you create and deploy the route, configure Clerk to send users to it during OAuth flows that require consent. + + + ### Open path settings + + In the [Clerk Dashboard](https://dashboard.clerk.com), open your application, select the relevant instance, and navigate to **Configure** > **Paths**. + + ### Set the OAuth consent location + + In the **OAuth consent** section, choose your custom location: + + - For a development instance, enter a path on the development host, such as `/oauth-consent`. + - For a production instance, enter an `https://` URL on your application domain, such as `https://example.com/oauth-consent`. + + Clerk only accepts production OAuth consent URLs that use HTTPS and belong to the same registrable domain as your instance domain, such as `example.com` or a subdomain of `example.com`. + + ### Keep OAuth application consent enabled + + Navigate to [**OAuth applications**](https://dashboard.clerk.com/~/oauth-applications) and confirm that the consent screen is enabled for every OAuth application that can use the custom route. + + If [dynamic client registration](/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth#dynamic-client-registration) is enabled, Clerk enforces the consent screen and does not allow it to be disabled. + + ### Test an authorization request + + Start an OAuth authorization flow for one of your OAuth applications. If the user is signed out, Clerk redirects to sign-in first, then redirects to your custom consent route with the original OAuth parameters. After the user allows or denies access, Clerk continues the OAuth flow and redirects back to the OAuth client's `redirect_uri`. + + +## Build a custom flow + +Building a [custom flow](!custom-flow) from low-level APIs is riskier than using ``. Only do this if you need a layout or interaction that the prebuilt component cannot support. + +The custom flow needs to do the same work as the prebuilt component: + +1. Read `client_id`, `redirect_uri`, `scope`, `state`, `nonce`, `code_challenge`, `code_challenge_method`, and any other OAuth authorization parameters from the incoming URL. +1. Load consent metadata for the signed-in user with `useOAuthConsent()` or `Clerk.oauthApplication.getConsentInfo()`. +1. Display the returned OAuth application name, logo URL, application URL, client ID, and scopes without changing their meaning. +1. Display the redirect destination safely. Show a short domain summary, and let the user inspect the full `redirect_uri`. +1. Preserve and forward the original OAuth authorization parameters when submitting the decision. +1. Submit a form with `method="POST"` to the URL returned by `Clerk.oauthApplication.buildConsentActionUrl({ clientId })`. +1. Use a submit field named `consented`, with `value="true"` for allow and `value="false"` for deny. +1. Include `organization_id` when the user grants access for a specific organization. +1. Preserve the user's ability to deny access. A denial returns an OAuth `access_denied` response to the OAuth client. + +Do not construct the [Frontend API URL](!frontend-api-url) by hand. Use `buildConsentActionUrl()` so Clerk can include the current session and development browser parameters that are required for the request. + +The following examples show the minimum shape of a custom consent page. They intentionally keep the UI plain so you can see the OAuth-specific requirements. + + + +These examples display the full redirect hostname and an expandable full URL. For a production custom flow, use a public-suffix-aware approach for root-domain summaries, handle IP addresses and localhost explicitly, and test long redirect URIs to make sure the real destination remains visible. + +These examples also do not implement organization selection. If an OAuth application can request `user:org:read`, use `` or add an organization selector that submits the selected `organization_id` with the allow action. + +The low-level `useOAuthConsent()` examples in this guide are available for Clerk's React-based SDKs. For Astro, Vue, and Nuxt, use the prebuilt `` component unless you are prepared to build directly against [ClerkJS](/docs/reference/javascript/overview) and maintain the consent flow yourself. + +## Security checklist + +Before shipping a custom consent page, verify the following: + +- The page is served over HTTPS in production. +- The page is on your application domain or subdomain, not on an unrelated domain. +- The page cannot be framed by untrusted sites. Use appropriate `Content-Security-Policy` `frame-ancestors` or equivalent headers. +- The page does not include third-party scripts that can read OAuth parameters or alter the consent form. +- The page displays the requesting OAuth application's identity and requested scopes before the user can allow access. +- The page prevents code injection from OAuth application-provided values, including application names, logo URLs, application URLs, and redirect URLs. +- The **Deny** action is visible and works. +- The page never grants consent automatically. +- The page forwards the original OAuth authorization parameters without allowing query parameters to override form-controlled fields such as `consented` or `organization_id`. +- The page uses `strict-origin-when-cross-origin` referrer policy. +- The OAuth application requests only the scopes it needs. +- Your team can audit OAuth applications and revoke suspicious grants if needed. + +## Prefer appearance for visual changes + +If your main goal is to change colors, fonts, spacing, or logos, use the [`appearance`](/docs/guides/customizing-clerk/appearance-prop/overview) prop with `` instead of building a custom flow. This keeps Clerk's consent behavior, redirect destination presentation, organization selection, form submission, and future security updates intact. + +Only build a low-level custom flow if: + +- The prebuilt component cannot support your required layout or interaction. +- You can maintain the route as a security-sensitive surface. +- You can show the requesting OAuth application's identity, requested scopes, and redirect destination clearly. +- You can preserve an equally visible deny action. + +Do not build a custom flow to hide scopes, hide redirect destinations, remove the deny action, or auto-approve trusted clients. Instead, model trust through OAuth application configuration, narrow scopes, and administrative review. Custom consent pages should make the consent decision clearer, not easier to miss. diff --git a/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth.mdx b/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth.mdx index 26f9b9745a..8e9059b265 100644 --- a/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth.mdx +++ b/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth.mdx @@ -83,6 +83,8 @@ The consent screen is enabled by default for all OAuth apps. You can enable or d > [!IMPORTANT] > 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. +> +> If you need to host the consent page on your own application domain, see [Customize the OAuth consent page](/docs/guides/configure/auth-strategies/oauth/custom-consent-page). Clerk strongly recommends using the default [Account Portal](/docs/guides/account-portal/overview) consent page unless you have a specific product requirement that it cannot satisfy. ## Token expiration and management diff --git a/docs/guides/configure/auth-strategies/oauth/scoped-access.mdx b/docs/guides/configure/auth-strategies/oauth/scoped-access.mdx index 263a5e407f..8729020b48 100644 --- a/docs/guides/configure/auth-strategies/oauth/scoped-access.mdx +++ b/docs/guides/configure/auth-strategies/oauth/scoped-access.mdx @@ -69,6 +69,8 @@ This guide provides step-by-step instructions on how to build OAuth scoped acces > The consent screen uses the scopes passed in the authorization request to inform the user exactly what they're granting access to, and to whom. > > By default, the consent screen is shown for all newly created OAuth apps, but this can be disabled in each app's settings [in the Clerk Dashboard](https://dashboard.clerk.com/~/oauth-applications), although not recommended. Learn more about [how Clerk's OAuth consent screen works](/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth#consent-screen-management). + > + > If you need to host the consent page yourself, see [Customize the OAuth consent page](/docs/guides/configure/auth-strategies/oauth/custom-consent-page). Clerk strongly recommends using the default [Account Portal](/docs/guides/account-portal/overview) consent page unless you have a specific product requirement that it cannot satisfy. Once you have accepted, you'll be redirected to the OAuth callback route, the `redirect_uri` specified earlier. This process will exchange the authorization code for an [access token](!oauth-access-token), and return the access token and refresh token as a JSON response, similar to this: diff --git a/docs/manifest.json b/docs/manifest.json index 36e2836308..6330f0796b 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -362,6 +362,10 @@ "title": "How Clerk implements OAuth", "href": "/docs/guides/configure/auth-strategies/oauth/how-clerk-implements-oauth" }, + { + "title": "Customize the OAuth consent page", + "href": "/docs/guides/configure/auth-strategies/oauth/custom-consent-page" + }, { "title": "Use OAuth for Single Sign-On (SSO)", "href": "/docs/guides/configure/auth-strategies/oauth/single-sign-on" diff --git a/docs/reference/components/authentication/oauth-consent.mdx b/docs/reference/components/authentication/oauth-consent.mdx index 6741b2edd4..9b1e2049f3 100644 --- a/docs/reference/components/authentication/oauth-consent.mdx +++ b/docs/reference/components/authentication/oauth-consent.mdx @@ -8,6 +8,8 @@ The `` component renders an OAuth consent screen for authenticat By default, `` reads the OAuth `client_id`, `scope`, and `redirect_uri` from the current URL. You can override the `client_id` and `scope` with props when rendering the component on a custom route. +For route setup guidance and security considerations, see [Customize the OAuth consent page](/docs/guides/configure/auth-strategies/oauth/custom-consent-page). + > [!IMPORTANT] > `` only renders for authenticated users. If the user is signed out, the component will not be displayed. @@ -20,6 +22,7 @@ The following example includes a basic implementation of the `` ```tsx {{ filename: 'app/oauth-consent/page.tsx' }} import { OAuthConsent } from '@clerk/nextjs' + import type { Metadata } from 'next' export const metadata: Metadata = { referrer: 'strict-origin-when-cross-origin', @@ -32,20 +35,17 @@ The following example includes a basic implementation of the `` - ```tsx {{ filename: 'src/routes/oauth-consent.tsx' }} + ```tsx {{ filename: 'src/OAuthConsentPage.tsx' }} import { OAuthConsent } from '@clerk/react' - export const meta = () => [ - { - name: 'referrer', - content: 'strict-origin-when-cross-origin', - }, - ] - export default function OAuthConsentPage() { return } ``` + + ```html {{ filename: 'index.html' }} + + ``` @@ -90,7 +90,7 @@ The following example includes a basic implementation of the `` - ```astro {{ filename: 'pages/oauth-consent.astro' }} + ```astro {{ filename: 'src/pages/oauth-consent.astro' }} --- import { OAuthConsent } from '@clerk/astro/components' --- @@ -107,7 +107,7 @@ The following example includes a basic implementation of the `` - ```vue {{ filename: 'oauth-consent.vue' }} + ```vue {{ filename: 'src/pages/OAuthConsentPage.vue' }}