Force a session token refresh
A user's session token is a short-lived JWT that Clerk refreshes every 60 seconds. However, there are some cases where you might want to force a refresh. For example, if you're retrieving information from the session token that has been updated but the token hasn't refreshed yet, the information is still old and you'll need to force the token to refresh.
There are two recommended approaches to force a user's session token to refresh:
Both perform a network request, but getToken({ skipCache: true })
will only get a new token, while user.reload()
will both get a new token and a new User
object.
getToken({ skipCache: true })
The getToken()
method retrieves the current user's session token. If skipCache
is set to true
, it will force a new token to be minted.
getToken()
is available on Clerk's authentication context, so it is accessible in both client-side and server-side code.
- Client-side: Access
getToken()
from the useSession() or useAuth() hooks. - Server-side: Access
getToken()
from the object.
user.reload()
The user.reload()
method refreshes the current user's User
object and their session token. It can be accessed from the useUser() hook.
Example
Say you're building a user profile page. You want to allow the user to update their information, and also allow them to set and update custom information. To store information about a user that Clerk doesn't collect, you can use metadata, which will get stored on the user's User
object.
When using metadata, it's recommended to store it in the user's session token to avoid making an API request to Clerk's Backend API when retrieving it.
However, when retrieving the user's metadata from the session token, because the session token refreshes every 60 seconds, the metadata may not be up to date if the token hasn't refreshed yet. In this case, you'd want to force a session token refresh. This is when you could use getToken({ skipCache: true })
.
The following example uses user.reload()
instead, because on this user's profile page, the metadata is not the only information the user can update. They can also update their first and last name. Therefore, you not only want to refresh the session token, which is where the metadata is retrieved from, but also the User
object, which is where the first and last name is retrieved from. This is why user.reload()
is the better option. If you were only retrieving the user's metadata, and not using the User
object, you could use getToken({ skipCache: true })
instead.
This example requires that the user's birthday has been configured to be stored in the session token like:
{
"birthday": "{{user.public_metadata.birthday}}"
}
To do so, follow the guide on customizing your session token.
The API route that uses Clerk's method to update the user's birthday metadata.
import { auth, clerkClient } from '@clerk/nextjs/server'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
// Use `req.json()` to parse the request body
const { birthday } = await req.json()
// Use `auth()` to access the current user's ID
const { userId } = await auth()
// Protect the route by checking if the user is signed in
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
// Use `clerkClient()` to access the Clerk client
const client = await clerkClient()
// Use the Backend SDK's `updateUserMetadata()` method to update the user's birthday
await client.users.updateUserMetadata(userId, {
publicMetadata: {
birthday,
},
})
return NextResponse.json({ success: true }, { status: 200 })
}
The client-side page that allows the user to update their first and last name and their birthday. It retrieves the user's birthday from the session token's claims under the birthday
key. To update the user's birthday, it calls the /update-user-metadata
API route, and then uses the method to refresh the user's session token and User
object.
'use client'
import { useState, useEffect } from 'react'
import { useAuth, useUser } from '@clerk/nextjs'
export default function Page() {
// Use `useAuth()` to access the authentication context,
// including the user's session claims, like `metadata`
const { isLoaded, userId, sessionClaims } = useAuth()
// Use `useUser()` to access the `User` object,
// which contains properties like `firstName`, `lastName`, and `publicMetadata`
const { user } = useUser()
const [birthday, setBirthday] = useState('')
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [status, setStatus] = useState<'loading' | 'success' | 'error' | null>(null)
const [error, setError] = useState<string | null>(null)
// Update state once the user's data loads
useEffect(() => {
if (user) {
setFirstName(user.firstName || '')
setLastName(user.lastName || '')
// Retrieve the user's birthday from the session token's claims under the `birthday` key
setBirthday(sessionClaims?.birthday || '')
}
}, [user, sessionClaims])
async function updateUserBirthday(birthday: string) {
try {
const response = await fetch('/api/update-user-metadata', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ birthday }),
})
const result = await response.json()
if (!response.ok) {
throw new Error(result.error || 'Failed to update birthday')
}
return { success: true }
} catch (err: any) {
return { error: err.message || 'Failed to update birthday' }
}
}
async function updateUserField(field: 'firstName' | 'lastName', value: string) {
try {
await user?.update({
[field]: value,
})
return { success: true }
} catch (err: any) {
return { error: err.message || `Failed to update ${field}` }
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setStatus('loading')
setError(null)
const updates = []
// Check which fields have been modified and need updating
if (firstName !== (user?.firstName || '')) {
updates.push(updateUserField('firstName', firstName))
}
if (lastName !== (user?.lastName || '')) {
updates.push(updateUserField('lastName', lastName))
}
if (birthday !== (sessionClaims?.metadata?.birthday || '')) {
updates.push(updateUserBirthday(birthday))
}
// If no fields have been filled or modified, show error
if (updates.length === 0) {
setError('Please fill in at least one field to update')
setStatus('error')
return
}
try {
// Execute all updates in parallel
const results = await Promise.all(updates)
// Check if any updates failed
const failedUpdates = results.filter((result) => result.error)
if (failedUpdates.length > 0) {
setError(failedUpdates.map((r) => r.error).join(', '))
setStatus('error')
} else {
user?.reload() // Refresh the session and the User object
setStatus('success')
}
} catch (err: any) {
setError(err.message || 'Failed to update user information')
setStatus('error')
}
}
// Check if Clerk has loaded
if (!isLoaded) return <div>Loading...</div>
// Check if the user is authenticated
if (!userId) return <div>Not authenticated</div>
return (
<div style={{ maxWidth: 400, margin: '2rem auto' }}>
<h1>Welcome, {userId}</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="firstName">First Name:</label>
<input
id="firstName"
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
<div>
<label htmlFor="lastName">Last Name:</label>
<input
id="lastName"
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
<div>
<label htmlFor="birthday">Birthday (YYYY-MM-DD):</label>
<input
id="birthday"
type="date"
value={birthday}
onChange={(e) => setBirthday(e.target.value)}
/>
</div>
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Updating...' : 'Update'}
</button>
</form>
{status === 'success' && <p style={{ color: 'green' }}>Updated successfully!</p>}
{status === 'error' && <p style={{ color: 'red' }}>{error}</p>}
</div>
)
}
Feedback
Last updated on