Skip to main content

Verify OAuth access tokens in your React Router application with Clerk

When building a resource server that needs to accept and verify issued by Clerk, it's crucial to verify these tokens on your backend to ensure the request is coming from an authenticated client.

Important

This guide assumes you've configured clerkMiddleware()React Router Icon in your React Router application. The getAuth() helper requires Clerk Middleware to be configured before it can read request authentication state.

Clerk's React Router SDK provides a built-in getAuth()React Router Icon function that supports token validation via the acceptsToken parameter. This lets you specify which type(s) of token your loader or action should accept in your React Router application.

By default, acceptsToken is set to session_token, which means OAuth tokens will not be accepted unless explicitly configured. You can pass either a single token type or an array of token types to acceptsToken. To learn more about the supported token types, see the getAuth() parameters documentationReact Router Icon.

Below are two examples of verifying OAuth access tokens in a React Router route module using Clerk's SDK:

Example 1: Accepting a single token type

In the following example, the acceptsToken parameter is set to only accept oauth_tokens.

  • If the token is invalid or missing, getAuth() will return false for isAuthenticated, and the request will be rejected with a 401 response.
  • If the token is valid, userId (the user who authorized the OAuth application) is returned along with the token's granted scopes for use in the application logic.
app/routes/api.example.ts
import { getAuth } from '@clerk/react-router/server'
import type { Route } from './+types/api.example'

export async function loader(args: Route.LoaderArgs) {
  const authObject = await getAuth(args, {
    acceptsToken: 'oauth_token',
  })

  // If isAuthenticated is false, the token is invalid
  if (!authObject.isAuthenticated) {
    throw new Response('Unauthorized', { status: 401 })
  }

  const { userId, scopes } = authObject

  return Response.json({ userId, scopes })
}

In the following example, the acceptsToken parameter allows both session_tokens and oauth_tokens.

  • If the token is invalid or missing, getAuth() will return false for isAuthenticated. Check isAuthenticated before reading the other properties, like userId.
  • If the token is an oauth_token, the code checks that it includes the required 'profile' scope. If not, the request is rejected with a 401 response.
  • If the token is valid and the required scope is present, isAuthenticated is true and userId is returned. Because the 'profile' scope grants access to the user's identity, this example uses the userId to fetch and return the user's profile data.
app/routes/api.example.ts
import { getAuth, clerkClient } from '@clerk/react-router/server'
import type { Route } from './+types/api.example'

export async function action(args: Route.ActionArgs) {
  // Accept both session_token and oauth_token types
  const authObject = await getAuth(args, {
    acceptsToken: ['session_token', 'oauth_token'],
  })

  // If isAuthenticated is false, the token is invalid
  if (!authObject.isAuthenticated) {
    throw new Response('Unauthorized', { status: 401 })
  }

  // Check if the token is an oauth_token and if it doesn't have the required scope
  if (authObject.tokenType === 'oauth_token' && !authObject.scopes.includes('profile')) {
    throw new Response('OAuth token missing the "profile" scope', { status: 401 })
  }

  // The 'profile' scope grants access to the user's identity,
  // so fetch and return the authenticated user's profile data
  const user = await clerkClient(args).users.getUser(authObject.userId)

  return Response.json({ id: user.id, firstName: user.firstName, lastName: user.lastName })
}

Feedback

What did you think of this content?

Last updated on