Tiesen LogoYuki UI

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