Authentication API
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /auth/register | Public | Create an account (role user) and return a token. |
| POST | /auth/login | Public | Exchange email and password for a token. |
| POST | /auth/google | Public | Exchange a Google ID token for a session. 503 when Google sign-in is not configured. |
| GET | /auth/me | Token | The current user. |
| POST | /auth/verify-email | Public | Confirm an emailed verification link. |
| POST | /auth/request-verification | Token | Resend the verification email. Always 200. |
| POST | /auth/forgot-password | Public | Start a password reset. Always 200, so it cannot probe which emails exist. |
| POST | /auth/reset-password | Public | Complete a reset with the emailed token and a new password. |
Sign in
Section titled “Sign in”curl -s https://<your-host>/auth/login \ -H 'content-type: application/json' \ -d '{"email":"you@example.com","password":"a-long-random-password"}'{ "token": "<jwt>", "user": { "id": "local", "email": "you@example.com", "role": "admin", "emailVerified": true, "createdAt": "2026-01-01T00:00:00.000Z" }}A wrong password returns 401 with a deliberately generic message. Password
hashes are never returned by any route.
Register
Section titled “Register”curl -s https://<your-host>/auth/register \ -H 'content-type: application/json' \ -d '{"email":"new@example.com","password":"at-least-eight-chars"}'Returns 201 with the same shape as login. New self-registered accounts get the
user role. Passwords must be at least 8 characters; a duplicate email returns
409.
The current user
Section titled “The current user”curl -s https://<your-host>/auth/me -H "authorization: Bearer $TOKEN"Email verification
Section titled “Email verification”POST /auth/verify-emailwith{ "token": "<token-from-the-link>" }— public, because the link is clicked while signed out. Verification tokens last 24 hours.POST /auth/request-verification— authenticated; resends the email. Always returns200, and does nothing if the address is already verified.
With no SMTP configured, the link is logged to the server console instead of sent, which is enough for local development.
Password reset
Section titled “Password reset”POST /auth/forgot-passwordwith{ "email": "..." }— public, and always returns200whether or not the address exists, so it cannot be used to discover accounts.POST /auth/reset-passwordwith{ "token": "...", "password": "..." }— completes the reset. Reset tokens last one hour and are single-use: changing the password invalidates any outstanding link.
Google sign-in
Section titled “Google sign-in”curl -s https://<your-host>/auth/google \ -H 'content-type: application/json' \ -d '{"idToken":"<google-id-token>"}'Enabled only when GOOGLE_OAUTH_CLIENT_IDS lists the client id that minted the
token; otherwise the route returns 503. The ID token is verified locally
against Google’s public keys, and its audience must be one of the configured
client ids.
Token handling
Section titled “Token handling”- Tokens are signed with
AUTH_JWT_SECRET(HS256) and carry the user id and role. - The algorithm is pinned, so an
alg: nonetoken is rejected. - There is no server-side session and no refresh flow: when a token expires, sign in again.
- Store tokens the way you would any credential — never in a URL or a log.