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.
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.
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 } })
})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),
})