Skip to Content
DevelopmentFrontend (Platform)Frontend Authentication

Authentication

This document explains the authentication system used in the Rhesis frontend application.

Authentication Architecture

The Rhesis frontend uses NextAuth.js to manage authentication sessions. The backend provides a native authentication system with support for:

  • Email/password login and registration
  • OAuth login (Google, GitHub)
  • Magic link (passwordless) login
  • Automatic access token refresh via refresh token rotation

All sensitive tokens (refresh tokens) are stored inside NextAuth’s httpOnly cookie and are never exposed to client-side JavaScript. Only the short-lived access token (15 minutes) is accessible to frontend components for API calls.

Authentication Flow

Email/Password Login

  1. User submits credentials via the AuthForm component
  2. Frontend calls POST /auth/login/email on the backend
  3. Backend validates credentials and returns an access token and refresh token
  4. Frontend calls signIn('credentials', \{ session_token, refresh_token \}) to establish the session
  5. NextAuth stores both tokens in an httpOnly cookie

OAuth Login (Google, GitHub)

  1. User clicks “Sign in with Google” or “Sign in with GitHub”
  2. Frontend redirects to GET /auth/login/\{provider\}
  3. Backend redirects to the OAuth provider
  4. After authentication, provider redirects to GET /auth/callback
  5. Backend wraps the tokens in a short-lived auth code (60 seconds) and redirects to the frontend
  6. Frontend exchanges the auth code via POST /auth/exchange-code
  7. Frontend calls signIn('credentials', \{ session_token, refresh_token \}) to establish the session
  1. User enters their email address
  2. Frontend calls POST /auth/magic-link
  3. Backend sends a single-use login link via email
  4. User clicks the link, frontend calls POST /auth/magic-link/verify
  5. Backend returns tokens, frontend establishes session via signIn()

Session Management

NextAuth stores its internal JWT (containing the access token, refresh token, and user data) in an httpOnly cookie:

src/auth.ts
cookies: {
sessionToken: {
    name: 'next-auth.session-token',
    options: {
      httpOnly: true,      // Cannot be read by JavaScript (XSS protection)
      sameSite: 'lax',
      path: '/',
      secure: FRONTEND_ENV !== 'development',
      maxAge: SESSION_DURATION_SECONDS,
    },
},
},

Automatic Token Refresh

The NextAuth jwt callback handles automatic access token refresh:

  1. On every request, NextAuth checks if the access token is within 60 seconds of expiry
  2. If so, it calls POST /auth/refresh with the stored refresh token
  3. The new access token and rotated refresh token replace the old values in the cookie
  4. This is transparent to the user — sessions are maintained for up to 7 days

If the refresh fails, token.error = 'RefreshTokenError' is set, which downstream components use to trigger re-authentication.

Route Protection

Routes are protected using middleware in src/middleware.ts:

  • Public routes: /auth/*, Quick Start landing page
  • Protected routes: All routes under /(protected)/ require a valid session
  • Onboarding redirect: Users without an organization are redirected to the onboarding flow

The middleware reads the NextAuth session cookie, verifies the session token with the backend via GET /auth/verify, and enforces route protection.

Authentication Components

AuthForm

The AuthForm component (components/auth/AuthForm.tsx) handles both login and registration:

  • Email/password form with validation
  • OAuth provider buttons (Google, GitHub) — shown only when providers are enabled
  • Magic link option
  • Toggle between login and registration modes

Provider Discovery

The frontend queries GET /auth/providers at startup to discover which authentication providers are enabled. OAuth buttons are only shown when the corresponding provider is configured on the backend.

Session Establishment Pattern

Every place that establishes a session uses NextAuth’s signIn() function:

example
const result = await signIn('credentials', {
session_token: tokenFromBackend,
refresh_token: refreshTokenFromBackend,
redirect: false,
});

This calls NextAuth’s server-side authorize callback, which:

  1. Sends POST /auth/verify with the access token to the backend
  2. Backend validates the JWT and returns user info
  3. NextAuth stores both tokens in the httpOnly cookie

Files that use this pattern:

  • components/auth/AuthForm.tsx — Email/password login and registration
  • app/auth/signin/page.tsx — OAuth callback (exchanges auth code for tokens)
  • app/auth/magic-link/page.tsx — Magic link verification
  • app/auth/verify-email/page.tsx — Email verification

Environment Variables

The frontend requires these authentication-related variables:

.env.local
# Required
NEXTAUTH_SECRET=your-nextauth-secret
NEXTAUTH_URL=http://localhost:3000
AUTH_SECRET=your-auth-secret
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080

# OAuth Providers (optional -- enables social login buttons)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GH_CLIENT_ID=your-github-client-id
GH_CLIENT_SECRET=your-github-client-secret

Security Considerations

  1. httpOnly cookies: Refresh tokens are never exposed to client-side JavaScript
  2. Short-lived access tokens: Access tokens expire in 15 minutes, limiting the impact of XSS
  3. HTTPS: Always use HTTPS in production to protect cookies in transit
  4. Token refresh: Automatic refresh ensures seamless sessions without long-lived tokens
  5. CORS: Backend CORS configuration restricts which origins can make API requests
  6. Error handling: Generic error messages prevent information leakage
  7. Auth code pattern: OAuth callbacks use a short-lived JWT auth code instead of exposing raw tokens in redirect URLs