Skip to main content
Docs

Customize your session token

Session tokens are JWTs generated by Clerk on behalf of your instance, and convey an authenticated user session to your backend.

By default, session tokens contain claims that are required for Clerk to function. You can learn more about these "default claims" in the session tokens documentation.

This guide will show you how to customize a session token to include additional claims that you may need in your application.

Caution

Clerk stores the session token in a cookie, and most browsers cap cookie size at 4KB. After accounting for the size of Clerk's default claims, the cookie can support up to 1.2KB of custom claims. Exceeding this limit will cause the cookie to not be set, which will break your app as Clerk depends on cookies to work properly. Learn more.

Add custom claims to your session token

  1. In the Clerk Dashboard, navigate to the Sessions page.
  2. Under Customize session token, in the Claims editor, you can add any claim to your session token that you need and select Save.

The following example adds the fullName and primaryEmail claims to the session token.

Clerk Dashboard showing the custom claim modal

Use the custom claims in your application

The AuthClerk Icon object includes a sessionClaims property that contains the custom claims you added to your session token. Accessing the Auth object differs depending on the SDK you're using. See the reference docClerk Icon for more information.

The following example demonstrates how to access the fullName and primaryEmail claims that were added to the session token in the last step.

For Next.js, the Auth object is accessed using the auth() helper in App Router apps and the getAuth() function in Pages Router apps. Learn more about using these helpers.

app/api/example/route.tsx
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'

export async function GET() {
  // Use `auth()` to access the user's session claims
  const { isAuthenticated, sessionClaims } = await auth()

  if (!isAuthenticated) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const fullName = sessionClaims.fullName

  const primaryEmail = sessionClaims.primaryEmail

  return NextResponse.json({ fullName, primaryEmail })
}
pages/api/example.ts
import { getAuth } from '@clerk/nextjs/server'
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // Use `getAuth()` to access the user's session claims
  const { isAuthenticated, sessionClaims } = getAuth(req)

  if (!isAuthenticated) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  const fullName = sessionClaims.fullName

  const primaryEmail = sessionClaims.primaryEmail

  return res.status(200).json({ fullName, primaryEmail })
}

For Astro, the Auth object is accessed using the locals.auth() function. Learn more about using locals.auth()Astro Icon.

src/api/example.ts
import type { APIRoute } from 'astro'

export const GET: APIRoute = async ({ locals }) => {
  // Use `locals.auth()` to access the user's session claims
  const { isAuthenticated, sessionClaims } = await locals.auth()

  if (!isAuthenticated) {
    return new Response('Unauthorized', { status: 401 })
  }

  const fullName = sessionClaims.fullName

  const primaryEmail = sessionClaims.primaryEmail

  return new Response(JSON.stringify({ fullName, primaryEmail }))
}

For Express, the Auth object is accessed using the getAuth() function. Learn more about using getAuth()Express.js Icon.

import { clerkMiddleware, getAuth, requireAuth } from '@clerk/express'
import express from 'express'

const app = express()
const PORT = 3000

// Apply `clerkMiddleware()` to all routes
app.use(clerkMiddleware())

const getSessionClaims = (req, res, next) => {
  // Use `getAuth()` to access the user's session claims
  const { isAuthenticated, sessionClaims } = getAuth(req)

  if (!isAuthenticated) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  const fullName = sessionClaims.fullName

  const primaryEmail = sessionClaims.primaryEmail

  return res.status(200).json({ fullName, primaryEmail })
}

app.get('/profile', requireAuth(), getSessionClaims)

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`)
})

For Go, the session claims are accessed using the SessionClaimsFromContext() function.

main.go
package main

import (
  "context"
  "fmt"
  "net/http"

  "github.com/clerk/clerk-sdk-go/v2"
  clerkhttp "github.com/clerk/clerk-sdk-go/v2/http"
  "github.com/clerk/clerk-sdk-go/v2/user"
)

type CustomSessionClaims struct {
   FullName string  `json:"fullName"`
   PrimaryEmail string `json:"primaryEmail"`
}

func customClaimsConstructor(ctx context.Context) any {
   return &CustomSessionClaims{}
}

func WithCustomClaimsConstructor(params *clerkHttp.AuthorizationParams) error {
   params.VerifyParams.CustomClaimsConstructor = customClaimsConstructor
   return nil
}

func main() {
  clerk.SetKey("YOUR_SECRET_KEY")

  mux := http.NewServeMux()
  protectedHandler := http.HandlerFunc(protectedRoute)
  mux.Handle(
    "/protected",
    clerkhttp.WithHeaderAuthorization(WithCustomClaimsConstructor)(protectedHandler),
  )

  http.ListenAndServe(":3000", mux)
}

func protectedRoute(w http.ResponseWriter, r *http.Request) {
  // Protect the route by checking if the session claims are present
  claims, ok := clerk.SessionClaimsFromContext(r.Context())
  if !ok {
    w.WriteHeader(http.StatusUnauthorized)
    w.Write([]byte(`{"access": "unauthorized"}`))
    return
  }

  // Access the custom claims
 customClaims, ok := claims.Custom.(*CustomSessionClaims)
  if !ok {
    // Handle the error how you see fit
  } else {
    fmt.Fprintf(w, `{"full_name": "%s", "primary_email": "%s"}`, customClaims.FullName, customClaims.PrimaryEmail)
  }
}

For React Router, the Auth object is accessed using the getAuth() function. Learn more about using getAuth()React Router Icon.

app/routes/profile.tsx
import { redirect } from 'react-router'
import { getAuth } from '@clerk/react-router/server'
import type { Route } from './+types/profile'

export async function loader(args: Route.LoaderArgs) {
  // Use `getAuth()` to access `isAuthenticated` and the user's ID and session claims
  const { isAuthenticated, sessionClaims } = await getAuth(args)

  if (!isAuthenticated) {
    return redirect('/sign-in?redirect_url=' + args.request.url)
  }

  const fullName = sessionClaims.fullName

  const primaryEmail = sessionClaims.primaryEmail

  return {
    fullName: JSON.stringify(fullName),
    primaryEmail: JSON.stringify(primaryEmail),
  }
}

export default function Profile({ loaderData }: Route.ComponentProps) {
  return (
    <div>
      <p>Welcome {loaderData.fullName}</p>
      <p>Your email is {loaderData.primaryEmail}</p>
    </div>
  )
}

For Tanstack React Start, the Auth object is accessed using the getAuth() function. Learn more about using getAuth()Tanstack Start Icon.

src/routes/api/example.ts
import { auth } from '@clerk/tanstack-react-start/server'
import { json } from '@tanstack/react-start'
import { createFileRoute } from '@tanstack/react-router'

export const ServerRoute = createFileRoute('/api/example')({
  server: {
    handlers: {
      GET: async () => {
        // Use `auth()` to access the user's session claims
        const { isAuthenticated, sessionClaims } = await auth

        if (!isAuthenticated) {
          return json({ error: 'Unauthorized' }, { status: 401 })
        }

        const fullName = sessionClaims.fullName

        const primaryEmail = sessionClaims.primaryEmail

        return json({ fullName, primaryEmail })
      },
    },
  },
})

Add global TypeScript type for custom session claims

To get auto-complete and prevent TypeScript errors when working with custom session claims, you can define a global type.

  1. In your application's root folder, add a types directory.
  2. Inside of the types directory, add a globals.d.ts file.
  3. Create the CustomJwtSessionClaims interface and declare it globally.
  4. Add the custom claims to the CustomJwtSessionClaims interface.

The following example demonstrates how to add the fullName and primaryEmail claims to the CustomJwtSessionClaims interface.

types/globals.d.ts
export {}

declare global {
  interface CustomJwtSessionClaims {
    fullName?: string
    primaryEmail?: string
  }
}

Feedback

What did you think of this content?

Last updated on