SpinAI uses a shared PIN gate instead of individual user accounts. This is a deliberate trade-off for a small internal team: there is no user registration, no password reset flow, and no per-user session management. Anyone who knows the team PIN gets full access, and the signed JWT they receive keeps them authenticated for 30 days without having to re-enter the PIN.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/fmoraga01/SpinAI/llms.txt
Use this file to discover all available pages before exploring further.
How it works
Middleware intercepts every request
The Next.js middleware (
middleware.ts) runs before every page and API route. It looks for a cookie named spinai_token on the incoming request. Routes under /api/auth are always allowed through unconditionally so that the login flow itself is never blocked.Missing or invalid token — show the PIN gate
If
spinai_token is absent, or if JWT verification fails (expired, tampered, wrong secret), the middleware sets the response header x-spinai-auth: 0 and lets the request continue. The root layout reads this header and renders the PinGate component in place of the normal application UI, prompting the user to enter the PIN.User submits PIN → POST /api/auth
The
PinGate component posts the entered PIN to POST /api/auth. The route handler compares the submitted value against the PIN environment variable using a case-insensitive comparison (pin.trim().toUpperCase() !== PIN.toUpperCase()). If the PIN does not match, the endpoint returns 401 with { "error": "PIN incorrecto" }.Success — sign and set the JWT cookie
On a valid PIN, the handler mints a signed JWT using the HS256 algorithm and the
JWT_SECRET environment variable. The token payload is minimal — { auth: true } — and is valid for 30 days. The signed token is written as an HttpOnly, Secure (in production), SameSite=lax cookie named spinai_token with path=/.Subsequent requests pass through transparently
The browser attaches
spinai_token on every subsequent request. The middleware calls jwtVerify from the jose library against the same JWT_SECRET. A valid token means the request continues to the application as normal — the user never sees the PIN gate again until the token expires or the cookie is cleared.Session details
| Property | Value |
|---|---|
| Cookie name | spinai_token |
| Signing algorithm | HS256 |
| Token expiry | 30 days |
HttpOnly | Yes |
Secure | Yes (production only) |
SameSite | lax |
| Cookie path | / |
Sign out
Sending aDELETE request to /api/auth clears the cookie immediately. The handler calls res.cookies.delete("spinai_token") and returns { ok: true }. The next page load will find no cookie and render the PIN gate.
Auth check endpoint
GET /api/auth/check lets client-side code verify whether the current session is still valid without triggering a full page reload. It reads the spinai_token cookie, runs jwtVerify, and returns one of two responses:
200 { authed: true }— token is present and valid401 { authed: false }— token is missing, expired, or invalid
API endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/auth | Validate PIN and issue a 30-day signed JWT cookie |
DELETE | /api/auth | Clear the spinai_token cookie (sign out) |
GET | /api/auth/check | Return { authed: true/false } for the current session |
Middleware configuration
The middleware runs on every route except Next.js internal paths. The matcher pattern uses a negative lookahead to exclude static assets, image optimisation routes, and the favicon — all of which should always be served without auth checks:/api/auth are excluded inside the middleware handler itself (not in the matcher), so the auth endpoints remain reachable even when no valid token is present.
Security notes
The PIN is compared case-insensitively by converting both the submitted value and the stored
PIN environment variable to uppercase before comparing. This means "spinai", "SPINAI", and "SpinAI" are all treated as the same PIN.