Overview
Auth.js sessions, signing in through django-allauth, and two-factor authentication.
HyperSaaS uses Auth.js (NextAuth.js v5) for the browser's session. Signing in goes through the Django backend's allauth API: the email and password, then, when two-factor authentication is on, a code from an authenticator app or one emailed to them. The Next.js server makes those calls and keeps the tokens; the browser never sees one.
Architecture
Browser Next.js Django (allauth)
│ │ │
│ email + password │ │
│───────────────────────────►│ POST /_allauth/app/v1/auth/login │
│ │────────────────────────────────────►│
│ │ ◄── tokens, or "code needed" ──────│
│ (code needed) │ │
│ ◄── "enter your code" ────│ session token kept in an │
│ │ httpOnly cookie for 5 minutes │
│ 6-digit code │ │
│───────────────────────────►│ POST .../auth/2fa/authenticate │
│ │────────────────────────────────────►│
│ │ ◄── access + refresh tokens ───────│
│ │ │
│ ◄── session cookie ───────│ Tokens kept in the encrypted │
│ │ Auth.js cookie │
│ API request │ │
│───────────────────────────►│ Authorization: Bearer {token} │
│ │────────────────────────────────────►│Key Components
| Component | Location | Purpose |
|---|---|---|
| Auth.js config | lib/auth/providers.ts | The token provider, refresh, sign-out |
| Sign-in actions | lib/auth/actions.ts | The password step, the code step, changing the password |
| allauth calls | lib/auth/allauth.ts | Calls allauth as the user; relayToAllauth for API routes |
| Tokens | lib/auth/tokens.ts, lib/auth/utils.ts | getAccessToken(), refresh, the browser-safe session |
| Proxy | proxy.ts | Protects routes, and saves refreshed tokens |
| Two-factor settings | components/settings/two-factor/ | The Security card, recovery codes, "confirm it's you" |
Auth Pages
| Page | Route | Description |
|---|---|---|
| Login | /login | Email + password, then a code if two-factor is on |
| Register | /register | Account creation → email verification |
| Activate | /user-activation/[uid]/[token] | Email verification link |
| Reset Password | /reset-password | Request password reset email |
| Confirm Reset | /confirm-password-reset/[uid]/[token] | Set new password |
| Resend Activation | /resend-activation | Request new activation email |
| Change Email | /reset-username | Emails a link to the current address |
| Choose New Email | /confirm-username-reset/[uid]/[token] | Sends a confirmation link to the new address |
| Confirm Email Change | /confirm-email-change/[token] | Makes the change; the old address is told |
A failed sign-in says why: a wrong email or password, an account that hasn't been activated yet, or too many attempts.
Session Shape
The session the browser can read says who you are, and nothing more:
interface Session {
user: {
id: string;
name?: string | null;
email?: string | null;
};
error?: "RefreshAccessTokenError";
}Server code gets the access token with getAccessToken(). Before a session reaches the browser, from /api/auth/session or the session passed to SessionProvider, the token is taken out (forBrowser in lib/auth/tokens.ts).
Plans belong to workspaces, so the session carries no subscription details; the workspace layout reads them from /api/workspaces/{id}/plan/. After the account settings change your name or email, the page calls useSession().update(), and the session reads them again from /auth/users/me/.
Token Refresh
The access token lasts 15 minutes. Auth.js refreshes it a minute before it expires, in its jwt callback, and keeps the new refresh token (each one works once). If refreshing fails, because you signed out elsewhere, the password changed or the session expired, the session ends and you sign in again. See Token Flow.