Authentication

Complete authentication system with email/password and OAuth providers

Fastly provides a complete authentication system with multiple sign-in methods, JWT-based sessions, and multi-device session tracking.

Authentication Methods

JWT Token System

The authentication system uses two JWT tokens with different lifetimes:

Prop

Type

Token Payload

interface TokenPayload {
  userId: string;
  email: string;
  type: 'access' | 'refresh';
  iat: number;
  exp: number;
}

Token Storage

Tokens are managed via the token manager at src/lib/config/token-manager.ts:

import { tokenManager } from '@/lib/config/token-manager';

// Store tokens after login
tokenManager.setTokens(accessToken, refreshToken, sessionId);

// Retrieve tokens
const accessToken = tokenManager.getAccessToken();
const refreshToken = tokenManager.getRefreshToken();
const sessionId = tokenManager.getSessionId();

// Clear tokens on logout
tokenManager.clearTokens();

Tokens are stored in localStorage. The axios interceptor automatically attaches them to API requests.

Session Tracking

Every successful authentication creates a session record with device metadata:

Prop

Type

Session Validation

Every authenticated request validates:

  1. Access token is valid and not expired
  2. Session ID exists in the X-Session-Id header
  3. Session is not revoked (revokedAt === null)
  4. Session belongs to the authenticated user
// Request headers for authenticated endpoints
{
  'Authorization': 'Bearer <access-token>',
  'X-Session-Id': '<session-id>'
}

Authentication Flow

User initiates authentication

User chooses email/password or OAuth provider on the login page.

Credentials validated

For email/password: credentials checked against database. For OAuth: authorization code exchanged for user data.

Email verification check

For email/password users, email must be verified before login completes. OAuth users are automatically verified.

Tokens generated

Access and refresh tokens are created with user payload.

const tokens = generateTokenPair(userId, email);
// { accessToken: '...', refreshToken: '...' }

Session created

A session record is created with device metadata from the request.

const session = await createUserSession({
  userAuthId,
  authMethod: 'email',
  request,
});

Response returned

Tokens, session ID, and user data are returned to the client.

{
  "accessToken": "eyJhbG...",
  "refreshToken": "eyJhbG...",
  "session": {
    "sessionId": "uuid-here",
    "browser": "Chrome 120",
    "os": "macOS 14"
  },
  "user": {
    "userId": "...",
    "email": "user@example.com",
    "firstName": "John"
  }
}

Token Refresh

When the access token expires, the client automatically refreshes it:

// POST /api/auth/refresh-token
{
  "refreshToken": "eyJhbG..."
}

// Headers
{
  "X-Session-Id": "session-uuid"
}

The axios interceptor handles this automatically. Failed refresh attempts redirect to login.

Frontend Hooks

useSession

Access current authentication state:

import { useSession } from '@/hooks/auth/useSession';

function Component() {
  const { user, isAuthenticated, isLoading } = useSession();

  if (isLoading) return <Loading />;
  if (!isAuthenticated) return <Redirect to="/log-in" />;

  return <div>Welcome, {user.firstName}</div>;
}

useAuthMutations

Authentication actions with React Query:

import { useLogin, useLogout } from '@/hooks/auth/useAuthMutations';

function LoginForm() {
  const login = useLogin();

  const handleSubmit = (data) => {
    login.mutate(data, {
      onSuccess: () => router.push('/dashboard'),
    });
  };
}

Available mutations:

  • useLogin() - Email/password login
  • useCreateAccount() - Registration
  • useVerifyEmail() - OTP verification
  • useResendVerification() - Resend OTP
  • useForgotPassword() - Request reset
  • useResetPassword() - Complete reset
  • useLogout() - End session

Route Protection

Client-side

Use the useRequireAuth hook:

import { useRequireAuth } from '@/hooks/auth/useRequireAuth';

function ProtectedPage() {
  useRequireAuth(); // Redirects to login if not authenticated

  return <Dashboard />;
}

Server-side

Use the authenticate middleware in API routes:

import { authenticate } from '@/lib/auth/auth-middleware';

export async function GET(request: Request) {
  const auth = await authenticate(request);

  if (!auth.success) {
    return auth.response; // Returns 401
  }

  const { user } = auth;
  // user.userId, user.email available
}

Authentication Pages

RoutePurposeFile
/create-accountNew user registrationsrc/app/(auth)/create-account/page.tsx
/log-inUser loginsrc/app/(auth)/log-in/page.tsx
/email-verificationOTP code entrysrc/app/(auth)/email-verification/page.tsx
/forgot-passwordRequest password resetsrc/app/(auth)/forgot-password/page.tsx
/reset-passwordSet new passwordsrc/app/(auth)/reset-password/page.tsx
/oauth-callbackHandle OAuth redirectssrc/app/(auth)/oauth-callback/page.tsx

Security Considerations

Tokens are stored in localStorage which is vulnerable to XSS attacks. For higher security requirements, consider httpOnly cookies.

Security features implemented:

  • Passwords hashed with bcrypt (10 salt rounds)
  • JWT secrets separate for access and refresh tokens
  • Session validation on every authenticated request
  • Session revocation capability
  • IP and device tracking
  • OTP expiration (10 minutes for email verification)
  • Password reset token expiration (10 minutes)

On this page