Skip to main content

Overview

The platform uses JSON Web Tokens (JWT) for session management with NextAuth.js. Sessions are configured with a 30-day expiration and include user information, authentication tokens, and custom claims.

Session Configuration

Sessions are configured in src/auth.ts:27:
session: {
  maxAge: 60 * 60 * 24 * 30, // 30 days (2,592,000 seconds)
  updateAge: 43200 // 12 hours (43,200 seconds)
},
jwt: {
  maxAge: 60 * 60 * 24 * 30 // 30 days
}

Configuration Parameters

session.maxAge
number
default:2592000
Maximum session duration in seconds (30 days). After this period, users must re-authenticate.
session.updateAge
number
default:43200
Session update interval in seconds (12 hours). The session is refreshed every 12 hours to extend its lifetime.
jwt.maxAge
number
default:2592000
JWT token maximum age in seconds (30 days). Must match or exceed session.maxAge.

JWT Token Structure

The JWT token is created and enriched in the jwt callback (src/auth.ts:55):
async jwt({ token, account, profile }) {
  if (account && profile) {
    const user = await db.user.findUnique({
      where: { email: profile.email! },
      select: { id: true, totalClasses: true }
    })
    
    if (user) {
      token.id = user?.id
      token.iat = Math.floor(Date.now() / 1000)
      token.exp = Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30
      token.accessToken = account.access_token
      
      if (profile.email === process.env.ADMIN_EMAIL! && account.refresh_token) {
        token.refreshToken = account.refresh_token
        await updateRefreshTokenInDb(user.id, token.refreshToken as string)
      }
    }
  }

  return token;
}

Token Claims

id
string
User’s database ID from the user table
sub
string
Subject - OAuth provider’s unique identifier for the user
jti
string
JWT ID - unique identifier for this specific token
iat
number
Issued At - Unix timestamp when the token was created
exp
number
Expiration - Unix timestamp when the token expires (30 days from iat)
accessToken
string
OAuth access token for making API calls to Google/GitHub
refreshToken
string
OAuth refresh token (only for admin users) - used to obtain new access tokens

Session Object Structure

The session callback (src/auth.ts:87) transforms the JWT into the session object:
async session({ session, token }: { session: any; token: any }) {
  if (token) {
    session.user.id = String(token.id)
    session.token = token
    session.user.sub = String(token.sub)
    session.user.jti = String(token.jti)
    session.user.iat = String(token.iat)
    session.user.exp = String(token.exp)
    session.user.accessToken = token.accessToken
    
    if (token.refreshToken && session.user.email === process.env.ADMIN_EMAIL) {
      session.user.refreshToken = token.refreshToken
    }
  }
  
  return session;
}

Session Schema

user
object
User information and authentication data
expires
string
ISO 8601 formatted expiration date/time
token
object
Complete JWT token object (contains all JWT claims)

Accessing Sessions

Server Components

import { auth } from '@/auth'

export default async function Page() {
  const session = await auth()
  
  if (!session) {
    return <div>Please sign in</div>
  }
  
  return (
    <div>
      <h1>Welcome, {session.user.name}!</h1>
      <p>Email: {session.user.email}</p>
      <p>Session expires: {session.expires}</p>
    </div>
  )
}

Server Actions

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

export async function getCurrentUser() {
  const session = await auth()
  
  if (!session?.user) {
    throw new Error('Not authenticated')
  }
  
  return {
    id: session.user.id,
    email: session.user.email,
    name: session.user.name
  }
}

API Routes

import { auth } from '@/auth'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const session = await auth()
  
  if (!session) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    )
  }
  
  return NextResponse.json({
    user: session.user
  })
}

Client Components (using next-auth/react)

'use client'
import { useSession } from 'next-auth/react'

export default function ClientComponent() {
  const { data: session, status } = useSession()
  
  if (status === 'loading') {
    return <div>Loading...</div>
  }
  
  if (status === 'unauthenticated') {
    return <div>Please sign in</div>
  }
  
  return <div>Signed in as {session?.user?.email}</div>
}

Session API Endpoints

Get Current Session

curl http://localhost:3000/api/auth/session \
  -H "Cookie: authjs.session-token=YOUR_SESSION_TOKEN"
Response:
{
  "user": {
    "id": "clx1234567890",
    "name": "John Doe",
    "email": "[email protected]",
    "image": "https://lh3.googleusercontent.com/a/...",
    "sub": "google-oauth2|123456789",
    "jti": "abc123xyz",
    "iat": "1709568000",
    "exp": "1712160000",
    "accessToken": "ya29.a0AfH6SMB..."
  },
  "expires": "2024-04-03T10:00:00.000Z"
}

Session Lifecycle

1

Authentication

User signs in via OAuth provider (Google/GitHub)
2

Token Creation

JWT token is created with user data and OAuth tokens (30-day expiration)
3

Session Storage

Encrypted session cookie is set in the browser with HTTP-only flag
4

Session Updates

Session is automatically refreshed every 12 hours (updateAge) to extend lifetime
5

Session Expiration

After 30 days (maxAge), session expires and user must re-authenticate

Session Refresh

Sessions are automatically refreshed based on the updateAge configuration:
  • updateAge: 43200 (12 hours)
  • When a session is accessed and more than 12 hours have passed since last update, NextAuth automatically refreshes it
  • The refresh extends the session lifetime by resetting the expiration time
  • This happens transparently without user interaction

Admin Sessions

Admin users (identified by ADMIN_EMAIL environment variable) receive additional privileges:
refreshToken
string
Google OAuth refresh token stored in both JWT and database

Admin Detection

if (profile.email === process.env.ADMIN_EMAIL! && account.refresh_token) {
  token.refreshToken = account.refresh_token
  await updateRefreshTokenInDb(user.id, token.refreshToken as string)
}

Checking Admin Status

import { auth } from '@/auth'

export async function isAdmin() {
  const session = await auth()
  
  return session?.user?.email === process.env.ADMIN_EMAIL
}

Session Security

Session tokens are stored in HTTP-only cookies, preventing access from client-side JavaScript and protecting against XSS attacks.
JWT tokens are encrypted using NextAuth’s built-in encryption, ensuring sensitive data like access tokens cannot be read by clients.
In production, session cookies use the Secure flag, ensuring they’re only transmitted over HTTPS.
Cookies are configured with SameSite attribute to prevent CSRF attacks.
Sessions are automatically refreshed every 12 hours, limiting the window of opportunity if a token is compromised.

Redirect Configuration

Custom redirect logic is defined in src/auth.ts:106:
async redirect({ url, baseUrl }: { url: string; baseUrl: string }) {
  if (url?.startsWith(baseUrl)) return url
  return '/inicio'
}
  • After successful sign-in, users are redirected to /inicio
  • If a return URL is specified and matches the base URL, that URL is used instead
  • This prevents open redirect vulnerabilities

Custom Pages

Session-related pages are customized in the configuration (src/auth.ts:111):
signIn
string
default:"/inicio"
Custom sign-in page path
signOut
string
default:""
Custom sign-out page (empty string uses default)
error
string
default:"/"
Error page for authentication failures

Testing Sessions

Get Session Info

# Get session via API
curl -X GET http://localhost:3000/api/auth/session \
  -H "Cookie: authjs.session-token=YOUR_TOKEN" \
  | jq

Check Token Expiration

import { auth } from '@/auth'

export async function checkExpiration() {
  const session = await auth()
  
  if (!session?.user?.exp) {
    return 'No session'
  }
  
  const expiresAt = parseInt(session.user.exp) * 1000
  const now = Date.now()
  const timeLeft = expiresAt - now
  
  return {
    expiresAt: new Date(expiresAt).toISOString(),
    timeLeftInHours: Math.floor(timeLeft / 1000 / 60 / 60)
  }
}

Common Issues

Solution: Wrap your app with <SessionProvider> from next-auth/react to enable client-side session access.
Solution: Ensure cookies are enabled in the browser and the domain/path settings are correct. Check that NEXTAUTH_URL is properly configured.
Solution: Check session.maxAge and jwt.maxAge configuration. Ensure they’re set to the desired duration in seconds.
Solution: Implement token refresh logic using the refresh token (admin only) or require the user to re-authenticate.

Next Steps

Google OAuth

Learn about Google OAuth implementation

Authentication Overview

Back to authentication overview

Build docs developers (and LLMs) love