Skip to content

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

POST /auth/login
Content-Type: application/json

{ "email": "you@example.com", "password": "..." }

Refresh

POST /auth/refresh
Cookie: doable_refresh_token=...

Returns a fresh access token. The browser does this transparently when a 401 is received.

Log out

POST /auth/logout

Clears both cookies. Tokens themselves are not revoked (stateless), so on shared devices users should also rely on the short access-token TTL.

Me

GET /auth/me
Authorization: Bearer <access>

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:

  1. Browser → GET /auth/github → redirects to GitHub.
  2. GitHub → GET /auth/github/callback?code=... → API exchanges code for tokens, finds-or-creates the user, sets cookies.
  3. 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.

See also