Skip to main content
Docs

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}}"
}

The API route that uses Clerk's method to update the user's birthday metadata.

app/api/update-user-metadata/route.ts
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.

app/user-profile/page.tsx
'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

What did you think of this content?

Last updated on