Skip to main content
Docs

Session tokens

When a user is authenticated in your application, Clerk generates a short-lived session token that you can use to authenticate requests to your backend. This token is a JSON Web Token (JWT) that contains information about the user and their session.

Read more about Clerk session tokens and how they work in the guide on how Clerk works.

Default claims

Important

You are reading about version 2 of Clerk's session token claims. To read about version 1, select the respective tab above.

Every generated token has default claims that cannot be overridden by templates. Clerk's default claims include:

ClaimAbbreviation expandedDescriptionExample
azpauthorized partyThe Origin header that was included in the original Frontend API request made from the user. Most commonly, it will be the URL of the application. This claim could be omitted if, for privacy-related reasons, Origin is empty or null.https://example.com
expexpiration timeThe time after which the token will expire, as a Unix timestamp. Determined using the Token lifetime JWT template setting in the Clerk Dashboard. See RFC 7519 for more information.1713158400
fvafactor verification ageEach item represents the minutes that have passed since the last time a first or second factor, respectively, was verified.[7, -1] which means it has been 7 minutes since the first factor was verified, and there either is not a second factor or the second factor has never been verified.
iatissued atThe time at which the token was issued as a Unix timestamp. See RFC 7519 for more information.1713158400
ississuerThe Frontend API URL of your instance. See RFC 7519 for more information.https://clerk.your-site.com for a production instance, https://your-site.clerk.accounts.dev for a development instance
jtiJWT IDThe unique identifier for the token. See RFC 7519 for more information.1234567890
nbfnot beforeThe time before which the token is considered invalid, as a Unix timestamp. Determined using the Allowed Clock Skew JWT template setting in the Clerk Dashboard. See RFC 7519 for more information.1713158400
sidsession IDThe ID of the current session.sess_123
subsubjectThe ID of the current user of the session. See RFC 7519 for more information.user_123
vversionThe version number of the session token.2
plaplanA comma-separated list of the plans that are active. Each entry is in the format scope:planslug, where scope can be u or o representing user or org-level plans, respectively. Will always have either one entry (if no org is active) or two entries (if a user and org are both active).u:free,o:pro
feafeaturesA list of enabled features and their scope. The scope can either be o for org-level features, u for user-level features, or uo for both.o:dashboard,o:impersonation

The o claim, or organization claim, is only included if the user is part of an organization and that organization is active. Its value is an object that contains the following properties:

ClaimAbbreviation expandedDescriptionExample
idIDThe ID of the organization.org_123
slgslugThe slug of the organization.org-slug
rolroleThe role of the user in the organization, without the org: prefix.admin
perpermissionsThe names of the permissions the user has in the organization.read,manage
fpmfeature-permission mapThe mapping of features with permissions, where the value of the integer, when converted to binary, represents a bitmask where each bit's position corresponds to a permission in the o.per list, and where 0 = not-allowed and 1 = allowed.3,2

Warning

The organization claims above are intentionally designed to be as compact as possible to keep JWT tokens small. As a result, they can be difficult to decode manually. We strongly recommend using one of our SDKs that support API version 2025-04-10 to handle decoding reliably.

The act (actor) claim is only included if the user is impersonating another user. It's value is an object that contains the following properties:

ClaimAbbreviation expandedDescriptionExample
ississuerThe referrer of the token.https://dashboard.clerk.com
sidsession IDThe session ID of the impersonated session.sess_456
subsubjectThe ID of the impersonator.user_456

Important

Version 1 was deprecated on April 14, 2025. To upgrade to version 2, go to the Updates page in the Clerk Dashboard.

Every generated token has default claims that cannot be overridden by templates. Clerk's default claims include:

ClaimAbbreviation expandedDescriptionExample
azpauthorized partyThe Origin header that was included in the original Frontend API request made from the user. Most commonly, it will be the URL of the application. This claim could be omitted if, for privacy-related reasons, Origin is empty or null.https://example.com
expexpiration timeThe time after which the token will expire, as a Unix timestamp. Determined using the Token lifetime JWT template setting in the Clerk Dashboard. See RFC 7519 for more information.1713158400
fvafactor verification ageEach item represents the minutes that have passed since the last time a first or second factor, respectively, was verified.[7, -1] which means it has been 7 minutes since the first factor was verified, and there either is not a second factor or the second factor has never been verified.
iatissued atThe time at which the token was issued as a Unix timestamp. See RFC 7519 for more information.1713158400
ississuerThe Frontend API URL of your instance. See RFC 7519 for more information.https://clerk.your-site.com for a production instance, https://your-site.clerk.accounts.dev for a development instance
nbfnot beforeThe time before which the token is considered invalid, as a Unix timestamp. Determined using the Allowed Clock Skew JWT template setting in the Clerk Dashboard. See RFC 7519 for more information.1713158400
sidsession IDThe ID of the current session.sess_123
subsubjectThe ID of the current user of the session. See RFC 7519 for more information.user_123

The following claims are only included if the user is part of an organization and that organization is active:

ClaimAbbreviation expandedDescriptionExample
org_idorganization IDThe ID of the active organization that the user belongs to.org_123
org_permissionsorganization permissionsThe permissions of the user in the currently active organization. System permissions are not included in the session token.["org:admin:example_permission", "org:member:example_permission"]
org_slugorganization slugThe slug of the currently active organization that the user belongs to.org-slug
org_roleorganization roleThe role of the user in the currently active organization.org:admin

The act (actor) claim is only included if the user is impersonating another user. It's value is an object that contains the following properties:

ClaimAbbreviation expandedDescriptionExample
ississuerThe referrer of the token.https://dashboard.clerk.com
sidsession IDThe session ID of the impersonated session.sess_456
subsubjectThe ID of the impersonator.user_456

Custom claims

If you would like to add custom claims to your session token, you can customize it.

You can also create custom tokens using a JWT template.

Size limitations

Clerk stores the session token in a cookie, and most browsers limit cookie size to 4KB. Exceeding this limit will cause the cookie to not be set, which will break your app as Clerk depends on cookies to work properly.

A session token with the default session claims won't run into this issue, as this configuration produces a cookie significantly smaller than 4KB. However, you must consider this limit when implementing a custom session token. After accounting for the size of Clerk's default claims, the cookie can support up to 1.2KB of custom claims.

Claims to monitor for size limits:

  • user.organizations
  • user.public_metadata
  • user.unsafe_metadata
  • org.public_metadata
  • org_membership.public_metadata

If you add any of these custom claims in your token, use caution to ensure the stored data doesn't exceed the size limit. It's highly recommended to store the extra data in your own database instead of storing it in metadata in the session token. If this isn't an option, you can move particularly large claims like these out of the token and fetch them using a separate API call from your backend.

Note

If your application encounters this issue, the Clerk Dashboard will display a warning: "Some users are exceeding cookie size limits". To resolve this, update your custom session token.

Example

It's recommended to keep the total size of custom claims in the session token under 1.2KB. The following example shows how to move particularly large claims out of the session token and fetch them using a separate API call from your backend. The limitations of this approach are that if you make this call to Clerk's Backend API frequently, you risk hitting rate limits and it's also slower than making a database query. We highly recommend storing the extra data in your own database instead of storing it in metadata in the session token.

For example, if you were storing several fields in user.public_metadata, like this:

// user.public_metadata
{
  onboardingComplete: true,
  birthday: '2000-01-01',
  country: 'Canada',
  bio: 'This is a bio -- imagine it is 6kb of written info',
}

Instead of storing all of that data in the session token, and possibly exceeding the 4KB limit, like this:

// Custom claims in the session token
{
  "metadata": "{{user.public_metadata}}"
}

You could store only the necessary data in the session token - for example, just the onboardingComplete field - like this:

// Custom claims in the session token
{
  "onboardingComplete": "{{user.public_metadata.onboardingComplete}}"
}

If you need to access the other fields, you can fetch them using a separate API call from your backend. The following example uses the method to access the current user's , which includes the publicMetadata field.

app/api/example/route.ts
import { auth, clerkClient } from '@clerk/nextjs/server'

export async function GET() {
  // Use `auth()` to get the user's ID
  const { userId } = await auth()

  // Protect the route by checking if the user is signed in
  if (!userId) {
    return new NextResponse('Unauthorized', { status: 401 })
  }

  const client = await clerkClient()

  // Use the Backend SDK's `getUser()` method to get the Backend User object
  const user = await client.users.getUser(userId)

  // Return the Backend User object
  return NextResponse.json({ user: user }, { status: 200 })
}
pages/api/auth.ts
import { getAuth, clerkClient } from '@clerk/nextjs/server'
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // Use getAuth() to get the user's ID
  const { userId } = getAuth(req)

  // Protect the route by checking if the user is signed in
  if (!userId) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  // Initialize the Backend SDK
  const client = await clerkClient()

  // Get the user's full Backend User object
  const user = await client.users.getUser(userId)

  return res.status(200).json({ user })
}
src/api/example.ts
import { clerkClient } from '@clerk/astro/server'

export async function GET(context) {
  // Use `locals.auth()` to get the user's ID
  const { userId } = context.locals.auth()

  // Protect the route by checking if the user is signed in
  if (!userId) {
    return new Response('Unauthorized', { status: 401 })
  }

  // Use the Backend SDK's `getUser()` method to get the Backend User object
  const user = await clerkClient(context).users.getUser(userId)

  // Return the Backend User object
  return new Response(JSON.stringify({ user }))
}
index.js
import { createClerkClient, getAuth } from '@clerk/express'
import express from 'express'

const app = express()
const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })

app.get('/user', async (req, res) => {
  // Use `getAuth()` to get the user's ID
  const { userId } = getAuth(req)

  // Protect the route by checking if the user is signed in
  if (!userId) {
    res.status(401).json({ error: 'User not authenticated' })
  }

  // Use the Backend SDK's `getUser()` method to get the Backend User object
  const user = await clerkClient.users.getUser(userId)

  // Return the Backend User object
  res.json(user)
})
src/routes/profile.tsx
import { redirect } from 'react-router'
import { getAuth } from '@clerk/react-router/ssr.server'
import { createClerkClient } from '@clerk/react-router/api.server'
import type { Route } from './+types/profile'

export async function loader(args: Route.LoaderArgs) {
  // Use `getAuth()` to get the user's ID
  const { userId } = await getAuth(args)

  // Protect the route by checking if the user is signed in
  if (!userId) {
    return redirect('/sign-in?redirect_url=' + args.request.url)
  }

  // Use the Backend SDK's `getUser()` method to get the Backend User object
  const user = await createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }).users.getUser(
    userId,
  )

  // Return the Backend User object
  return {
    user: JSON.stringify(user),
  }
}
routes/profile.tsx
import { LoaderFunction, redirect } from '@remix-run/node'
import { getAuth } from '@clerk/remix/ssr.server'
import { createClerkClient } from '@clerk/remix/api.server'

export const loader: LoaderFunction = async (args) => {
  // Use `getAuth()` to get the user's ID
  const { userId } = await getAuth(args)

  // If there is no userId, then redirect to sign-in route
  if (!userId) {
    return redirect('/sign-in?redirect_url=' + args.request.url)
  }

  // Use the Backend SDK's `getUser()` method to get the Backend User object
  const user = await createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }).users.getUser(
    userId,
  )

  // Return the Backend User object
  return { serialisedUser: JSON.stringify(user) }
}
routes/profile.tsx
import { ActionFunction, redirect } from '@remix-run/node'
import { getAuth } from '@clerk/remix/ssr.server'
import { createClerkClient } from '@clerk/remix/api.server'

export const action: ActionFunction = async (args) => {
  // Use `getAuth()` to get the user's ID
  const { userId } = await getAuth(args)

  // If there is no userId, then redirect to sign-in route
  if (!userId) {
    return redirect('/sign-in?redirect_url=' + args.request.url)
  }

  // Prepare the data for the mutation
  const params = { firstName: 'John', lastName: 'Wicker' }

  // // Use the Backend SDK's `updateUser()` method to update the Backend User object
  const updatedUser = await createClerkClient({
    secretKey: process.env.CLERK_SECRET_KEY,
  }).users.updateUser(userId, params)

  // Return the updated user
  return { serialisedUser: JSON.stringify(updatedUser) }
}
app/routes/api/example.tsx
import { createClerkClient } from '@clerk/backend'
import { json } from '@tanstack/react-start'
import { createAPIFileRoute } from '@tanstack/react-start/api'
import { getAuth } from '@clerk/tanstack-react-start/server'

export const Route = createAPIFileRoute('/api/example')({
  GET: async ({ request, params }) => {
    // Use `getAuth()` to get the user's ID
    const { userId } = await getAuth(req)

    // Protect the route by checking if the user is signed in
    if (!userId) {
      return json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Instantiate the Backend SDK
    const clerkClient = createClerkClient({ secretKey: import.meta.env.CLERK_SECRET_KEY })

    // Use the Backend SDK's `getUser()` method to get the Backend User object
    const user = userId ? await clerkClient.users.getUser(userId) : null

    // Return the Backend User object
    return json({ user })
  },
})

Validate session tokens

If you're using the middleware provided by our Clerk SDKs, validating session tokens is handled automatically in every request. If you're not using the middleware, you can still use the respective helpers provided by the SDKs to validate the tokens.

To learn how to manually verify a session token, refer to the dedicated guide.

Feedback

What did you think of this content?

Last updated on