Authentication¶
Doable uses JWT-based session tokens. There are no server-side sessions; tokens are stateless.
Token shape¶
| Token | TTL (default) | Stored | Used by |
|---|---|---|---|
| Access token | JWT_ACCESS_TOKEN_EXPIRES_IN (default 15m) |
HttpOnly cookie doable_access_token, also returned in JSON for non-browser clients |
Every authenticated API call |
| Refresh token | JWT_REFRESH_TOKEN_EXPIRES_IN (default 7d) |
HttpOnly cookie doable_refresh_token |
Refreshing the access token |
Both are signed HS256 with JWT_SECRET, issued by JWT_ISSUER (default doable).
Endpoints¶
Sign up (email + password)¶
POST /auth/signup
Content-Type: application/json
{
"email": "you@example.com",
"password": "at-least-8-chars",
"displayName": "Optional"
}
Returns { user, accessToken, refreshToken } and sets the cookies. Creates the user's personal workspace.
Log in¶
Refresh¶
Returns a fresh access token. The browser does this transparently when a 401 is received.
Log out¶
Clears both cookies. Tokens themselves are not revoked (stateless), so on shared devices users should also rely on the short access-token TTL.
Me¶
Returns the current user, their default workspace, and feature flags.
OAuth (GitHub, Google)¶
Configure providers in .env:
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
GITHUB_REDIRECT_URI=https://<api>/auth/github/callback
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://<api>/auth/google/callback
Flow:
- Browser →
GET /auth/github→ redirects to GitHub. - GitHub →
GET /auth/github/callback?code=...→ API exchanges code for tokens, finds-or-creates the user, sets cookies. - The user lands on
NEXT_PUBLIC_APP_URL/dashboard.
Same shape for Google. New providers go under services/api/src/routes/auth/.
OAuth for "Bring your own GitHub Copilot"¶
Separate flow at /auth/github/copilot/callback (GITHUB_COPILOT_REDIRECT_URI). Used only when a workspace member wants to spend their own Copilot quota instead of the platform's. The token is encrypted with ENCRYPTION_KEY and stored in ai_provider_keys.
Password reset¶
POST /auth/forgot-password { "email": "you@example.com" }
POST /auth/reset-password { "token": "...", "password": "new" }
The reset email is sent via the configured mailer (see services/api/src/lib/email/).
Rate limits & lockout¶
Auth routes have stricter rate limits than the global default — to slow brute-force attempts. fail2ban (installed by setup-server.sh) watches the auth log file for repeated 401s.
Using the API from scripts¶
TOKEN=$(curl -s https://<api>/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"...","password":"..."}' \
| jq -r .accessToken)
curl https://<api>/projects -H "Authorization: Bearer $TOKEN"
For long-running automations, generate API tokens from User Settings → Developer → API tokens — these have configurable scopes and don't expire.