āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/guides/sessions/customize-session-tokens ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
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.
<Include src="_partials/token-size-callout" /> <Steps> ## Add custom claims to your session tokenThe following example adds the fullName and primaryEmail claims to the session token.

The Auth object includes a sessionClaims property that contains the custom claims you added to your session token. Accessing the Auth object differs depending on the framework you are using. See the reference doc 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.
<Tabs items={["Next.js", "Astro", "Express", "Go", "React Router", "Remix", "Tanstack React Start"]}>
<Tab>
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.
<CodeBlockTabs options={["App Router", "Pages Router"]}>
```tsx {{ filename: 'app/api/example/route.tsx' }}
import { auth } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
export async function GET() {
const { sessionClaims } = await auth()
const fullName = sessionClaims.fullName
const primaryEmail = sessionClaims.primaryEmail
return NextResponse.json({ fullName, primaryEmail })
}
```
```tsx {{ filename: '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 `isAuthenticated` and the user's ID and session claims
const { sessionClaims } = getAuth(req)
const fullName = sessionClaims.fullName
const primaryEmail = sessionClaims.primaryEmail
return res.status(200).json({ fullName, primaryEmail })
}
```
</CodeBlockTabs>
</Tab>
<Tab>
For Astro, the `Auth` object is accessed using the `locals.auth()` function. [Learn more about using `locals.auth()`](/docs/astro/guides/users/reading#server-side).
```tsx {{ filename: 'src/api/example.ts' }}
import type { APIRoute } from 'astro'
export const GET: APIRoute = async ({ locals }) => {
// Use `locals.auth()` to access `isAuthenticated` and the user's ID and session claims
const { isAuthenticated, userId, sessionClaims } = await locals.auth()
// Protect the route by checking if the user is signed in
if (!isAuthenticated) {
return new Response('Unauthorized', { status: 401 })
}
const fullName = sessionClaims.fullName
const primaryEmail = sessionClaims.primaryEmail
return new Response(JSON.stringify({ fullName, primaryEmail }))
}
```
</Tab>
<Tab>
For Express, the `Auth` object is accessed using the `getAuth()` function. [Learn more about using `getAuth()`](/docs/reference/express/overview#get-auth).
```js
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())
// Use `getAuth()` to get the session claims
const getSessionClaims = (req, res, next) => {
const { sessionClaims } = getAuth(req)
const fullName = sessionClaims.fullName
const primaryEmail = sessionClaims.primaryEmail
return res.status(200).json({ fullName, primaryEmail })
}
app.get('/profile', requireAuth(), getSessionClaims)
// Start the server and listen on the specified port
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`)
})
```
</Tab>
<Tab>
For Go, the session claims are accessed using the [`SessionClaimsFromContext()`](https://pkg.go.dev/github.com/clerk/clerk-sdk-go/v2#SessionClaimsFromContext) function.
```go {{ filename: '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("{{secret}}")
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)
}
}
```
</Tab>
<Tab>
For React Router, the `Auth` object is accessed using the `getAuth()` function. [Learn more about using `getAuth()`](/docs/react-router/guides/users/reading#server-side).
```tsx {{ filename: '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)
// Protect the route by checking if the user is signed in
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>
)
}
```
</Tab>
<Tab>
For Remix, the `Auth` object is accessed using the `getAuth()` function. [Learn more about using `getAuth()`](/docs/remix/guides/users/reading#get-auth).
```tsx {{ filename: '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 access `isAuthenticated` and the user's ID and session claims
const { isAuthenticated, userId, sessionClaims } = await getAuth(args)
// Protect the route by checking if the user is signed in
if (!isAuthenticated) {
return redirect('/sign-in?redirect_url=' + args.request.url)
}
const fullName = sessionClaims.fullName
const primaryEmail = sessionClaims.primaryEmail
return { fullName, primaryEmail }
}
```
</Tab>
<Tab>
For Tanstack React Start, the `Auth` object is accessed using the `getAuth()` function. [Learn more about using `getAuth()`](/docs/tanstack-react-start/guides/users/reading#server-side).
```ts {{ filename: 'src/routes/api/example.ts' }}
import { getAuth } from '@clerk/tanstack-react-start/server'
import { json } from '@tanstack/react-start'
import { createServerFileRoute } from '@tanstack/react-start/server'
export const ServerRoute = createServerFileRoute().methods({
GET: async ({ request, params }) => {
// Use `getAuth()` to access `isAuthenticated` and the user's ID and session claims
const { isAuthenticated, userId, sessionClaims } = await getAuth(request)
// Protect the API route by checking if the user is signed in
if (!isAuthenticated) {
return json({ error: 'Unauthorized' }, { status: 401 })
}
const fullName = sessionClaims.fullName
const primaryEmail = sessionClaims.primaryEmail
return json({ fullName, primaryEmail })
},
})
```
</Tab>
</Tabs>
To get auto-complete and prevent TypeScript errors when working with custom session claims, you can define a global type.
types directory.types directory, add a globals.d.ts file.CustomJwtSessionClaims interface and declare it globally.CustomJwtSessionClaims interface.The following example demonstrates how to add the fullName and primaryEmail claims to the CustomJwtSessionClaims interface.
export {}
declare global {
interface CustomJwtSessionClaims {
fullName?: string
primaryEmail?: string
}
}
</Steps>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā