API Authentication
PensionPortal.ai uses session-based authentication powered by Auth.js v5 (NextAuth). There are no standalone API keys for external integrations in the current release — all API access requires a valid authenticated session.
A bearer-token API key system is on the roadmap for Phase 2. For now, all programmatic access must go through the session cookie flow described below.
Session Flow
1. Sign In
POST /api/auth/signin
Content-Type: application/x-www-form-urlencoded
csrfToken=<token>&email=user@example.com&password=...&callbackUrl=/dashboard
On success, Auth.js sets a __Secure-next-auth.session-token cookie (or next-auth.session-token in development). Include this cookie in all subsequent requests.
2. Make Authenticated Requests
Pass the session cookie automatically (browser) or explicitly (server-to-server):
curl -X GET https://your-deployment.vercel.app/api/schemes \
-H "Cookie: next-auth.session-token=<token>"
3. Sign Out
Role-Based Access Control (RBAC)
Every user is assigned exactly one role. Roles determine which API endpoints are accessible and which data is visible.
| Role | Description | Key Permissions |
|---|
SuperAdmin | Platform administrator | Full access across all tenants |
BrokerAdmin | Broker firm administrator | Full access within their tenant; can approve schemes |
BrokerUser | Broker team member | Read/write within tenant; cannot approve |
EmployerUser | Employer representative | Access to their own employer’s data only |
Member | Scheme member | Read-only access to own member record |
Role enforcement happens in API route handlers via requireRole() from src/lib/actor-context.ts. Requests with insufficient role return 403 Forbidden.
Tenant Isolation
Users are scoped to a single tenant (broker firm). The tenant context is derived from the session — you cannot query another tenant’s data by passing a different brokerId in the request body. Server-side enforcement is absolute.
// 403 response when accessing another tenant's resource:
{
"error": "Forbidden — resource does not belong to your organisation"
}
Unauthenticated Requests
Requests to protected endpoints without a valid session receive:
// 401 Unauthorized
{
"error": "Unauthenticated"
}
Password Reset
POST /api/auth/forgot-password
Content-Type: application/json
{ "email": "user@example.com" }
If the email exists, a reset link is sent via Resend. The link calls:
POST /api/auth/reset-password
Content-Type: application/json
{ "token": "<reset-token>", "password": "<new-password>" }
Security Notes
- HTTPS only in production — session cookies are
Secure and HttpOnly
- CSRF protection — all mutating requests require a valid CSRF token (handled automatically by Auth.js)
- Session expiry — sessions expire after 30 days of inactivity
- No credential logging — authentication errors return generic messages; detailed errors are logged server-side only
Development Credentials
For local development and demo environments only:
| Email | Password | Role |
|---|
admin@iorp-ii-app.ie | admin123 | SuperAdmin |
broker@iorp-ii-app.ie | broker123 | BrokerAdmin |
employer@iorp-ii-app.ie | employer123 | EmployerUser |
Never use development credentials in production. Rotate AUTH_SECRET before deploying to Vercel.