> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pensionsportal.ie/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication — NextAuth / Auth.js

> How PensionsPortal.ie authenticates users with Auth.js v5 (NextAuth), JWT sessions, and bcrypt password hashing.

PensionsPortal.ie uses **Auth.js v5** (formerly NextAuth) with the Credentials provider. Users authenticate with email and bcrypt-hashed passwords stored in Neon PostgreSQL. Sessions are issued as signed JWTs, carrying role and tenancy claims.

## Architecture

```
Browser → POST /api/auth/signin
         → Credentials provider
         → DB lookup (Drizzle ORM)
         → bcrypt.compare()
         → JWT issued (role, brokerId, employerId)
         → HttpOnly session cookie
```

## Configuration (`src/lib/auth.ts`)

The Auth.js configuration lives in `src/lib/auth.ts` and exports `{ handlers, auth, signIn, signOut }`.

### Session Strategy

```ts theme={null}
session: {
  strategy: "jwt",
}
```

JWT sessions are used. No database session table is required, reducing attack surface and eliminating session fixation risks tied to server-side session stores.

### Custom JWT Claims

The `jwt` and `session` callbacks propagate three pension-specific fields from the user record into every token and session:

| Claim        | Type             | Purpose                                     |
| ------------ | ---------------- | ------------------------------------------- |
| `role`       | `UserRole`       | Drives RBAC throughout the application      |
| `brokerId`   | `string \| null` | Scopes data queries to the correct broker   |
| `employerId` | `string \| null` | Scopes data queries to the correct employer |

```ts theme={null}
async jwt({ token, user }) {
  if (user) {
    token.id = user.id
    token.role = user.role
    token.brokerId = user.brokerId
    token.employerId = user.employerId
  }
  return token
}
```

### Password Hashing

Passwords are hashed with **bcryptjs** before storage. On authentication, `bcrypt.compare()` is used — timing-safe by design. Plaintext passwords are never logged, stored, or transmitted.

### Login Page

Auth.js is configured to redirect unauthenticated requests to `/auth/login`:

```ts theme={null}
pages: {
  signIn: "/auth/login",
}
```

## Auth Flow

<Steps>
  <Step title="Credential submission">
    User submits email + password to `POST /api/auth/signin` (handled by Auth.js route handler at `src/app/api/auth/[...nextauth]/route.ts`).
  </Step>

  <Step title="Database lookup">
    Drizzle ORM queries the `users` table by email. If `DATABASE_URL` is not set, the request fails in production.
  </Step>

  <Step title="Password verification">
    `bcrypt.compare(providedPassword, storedHash)` — returns false if the hash does not match. Wrong passwords do **not** fall through to any fallback.
  </Step>

  <Step title="JWT issuance">
    On success, Auth.js issues a signed JWT containing `id`, `role`, `brokerId`, and `employerId`. The JWT secret is `AUTH_SECRET` (required environment variable).
  </Step>

  <Step title="Session cookie">
    The JWT is stored in an HttpOnly, Secure, SameSite=Lax cookie. It is not accessible to JavaScript.
  </Step>
</Steps>

## Role Mapping

The database schema uses granular role names; Auth.js maps these to application-level `UserRole` values:

| Schema Role   | Application Role |
| ------------- | ---------------- |
| `SuperAdmin`  | `admin`          |
| `BrokerAdmin` | `broker`         |
| `BrokerUser`  | `broker`         |
| `Trustee`     | `employer`       |

## Development vs Production

<Warning>
  Auth.js includes a dev-only credential fallback (hardcoded test users). This fallback is **explicitly disabled in production** via a `NODE_ENV === "production"` guard. Never deploy without `DATABASE_URL` set in production.
</Warning>

In production:

* `DATABASE_URL` must be set — the DB fallback is the only auth path
* `AUTH_SECRET` must be a cryptographically random string (minimum 32 bytes)
* The dev credential fallback returns `null` immediately

## Environment Variables

| Variable       | Required     | Description                                                    |
| -------------- | ------------ | -------------------------------------------------------------- |
| `AUTH_SECRET`  | ✅            | Signs and verifies JWTs. Generate with `openssl rand -hex 32`. |
| `AUTH_URL`     | ✅ Production | Canonical URL for Auth.js callbacks                            |
| `DATABASE_URL` | ✅ Production | Neon PostgreSQL connection string                              |

## TypeScript Augmentation

Custom session fields are typed via module augmentation in `src/lib/auth-types.ts`, ensuring TypeScript catches any access to undefined session properties at compile time.

```ts theme={null}
declare module "next-auth" {
  interface Session {
    user: PensionsUser & DefaultSession["user"]
  }
}
```
