Usage

How to use the authentication system in your Next.js application

This guide explains how to use the authentication system in your Next.js application, including session retrieval, sign-in, sign-out, and usage in React components.

Get session data

Use the auth function to retrieve the current session. This can be done in API routes or server components.

import { headers } from 'next/headers'

import { auth, currentUser } from '@/server/auth'

// In API Route
const session = await auth({ headers: request.headers })
// or in Next.js App Router
const session = await auth({ headers: await headers() })
//      ^ { user: User | null; expiresAt: Date }

// Get the current user directly in API Route
const user = await currentUser({ headers: request.headers })
// or in Next.js App Router
const user = await currentUser({ headers: await headers() })

Sign in

Call signIn with user credentials to authenticate and receive a token. Store the token in a secure, HTTP-only cookie.

import { cookies } from 'next/headers'

import { signIn } from '@/server/auth'

const result = await signIn({ email, password })
//     ^ { token: string; expiresAt: Date }

// Store the token in a cookie for subsequent requests
;(await cookies()).set('auth.token', result.token, {
  path: '/',
  httpOnly: true,
  sameSite: 'lax',
  secure: process.env.NODE_ENV === 'production',
  expires: result.expiresAt,
})

Sign out

Call signOut to invalidate the session and remove the authentication token.

import { cookies, headers } from 'next/headers'

import { signOut } from '@/server/auth'

await signOut({ headers: request.headers })
// or in Next.js App Router
await signOut({ headers: await headers() })

// Clear the auth token cookie
;(await cookies()).delete('auth.token')

Use in React components

Use the useSession hook to access authentication state and actions in client components.

'use client'

import { useSession } from '@/hooks/use-session'

export const UserProfile = () => {
  const { status, session, signIn, signOut, refreshToken } = useSession()

  if (status === 'loading') return <div>Loading...</div>
  if (status === 'unauthenticated')
    return <button onClick={() => signIn({ email, password })}>Sign In</button>

  return (
    <div>
      <p>Welcome, {session.user.name}!</p>
      <button onClick={() => signOut()}>Sign Out</button>
      <button onClick={() => refreshToken()}>Refresh Token</button>
    </div>
  )
}

Using @tanstack/react-query for refreshing access tokens

With Next.js API Routes

When using Next.js API routes, you can implement token refresh logic in your React Query hooks. For example, if you have a protected API route that requires authentication, you can handle token refresh in the retry option of useQuery.

app/api/protected-data/route.ts
import { verifyAccessToken } from '@/server/auth'

export async function GET(request: Request) {
  const token =
    request.cookies.get('auth.access_token')?.value ??
    request.headers.get('Authorization')?.replace('Bearer ', '')

  const user = await verifyAccessToken(token)
  if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 })

  return Response.json({ data: 'Protected data' })
}
import { useQuery } from '@tanstack/react-query'

useQuery({
  queryKey: ['protectedData'],
  queryFn: async () => {
    const response = await fetch('/api/protected-data')
    const json = await response.json()

    if (response.status === 401) throw new Error(json.error ?? 'Unauthorized')
    return response.json()
  },
  retry(failureCount, error) {
    if (error.message === 'Unauthorized') {
      fetch('/api/auth/refresh-token', { method: 'POST' })
      return failureCount < 1
    }

    return failureCount < 3
  },
})

With tRPC

When using tRPC, you can implement token refresh logic in a custom link. This allows you to automatically attempt to refresh the token when an unauthorized error is encountered.

server/trpc.ts
import { initTRPC } from '@trpc/server'
import { verifyAccessToken } from '@/server/auth'

const createTRPCContext = async ({ headers }: { headers: Headers }) => {
  const token =
    headers.get('Authorization')?.replace('Bearer ', '') ??
    headers
      .get('Cookie')
      ?.split(';')
      .find((cookie) => cookie.trim().startsWith('auth.access_token='))
      ?.split('=')[1]

  const user = await verifyAccessToken(token)
  return { user }
}

const t = initTRPC.context<typeof createTRPCContext>().create()

const protectedProcedure = t.procedure.use(({ ctx, meta, next }) => {
  if (!ctx.user?.id) throw new TRPCError({ code: 'UNAUTHORIZED' })
  return next({ ctx: { user: ctx.user } })
})
trpc/client.ts
import { retryLink } from '@trpc/client'

retryLink({
  retry: ({ error, attempts }) => {
    if (error.data?.code === 'UNAUTHORIZED') {
      fetch(`${getBaseUrl()}/api/auth/refresh-token`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${getSessionToken()}`,
        },
      })

      return attempts < 1 // Retry once for unauthorized errors after attempting token refresh
    }

    return attempts < 3
  },
  retryDelayMs: (attempts) => Math.min(1000 * 2 ** attempts, 30000),
})

On this page