Skip to main content

Authentication

Ledgerline uses session-based authentication with HTTP-only cookies. Two auth flows are supported: Discord OAuth 2.0 and local credential-based login.

Prerequisites

  • Network access to the Ledgerline instance.
  • For Discord OAuth: a Discord account and a configured Discord auth provider on the target tenant.
  • For local auth: LOCAL_AUTH_ENABLED=true on the server and valid credentials.

All authenticated API requests use the ledgerline_auth cookie. This cookie is:

  • HTTP-only — not accessible via JavaScript.
  • Signed — tamper-resistant.
  • Cache-backed — session state is stored server-side (Valkey/Redis in production, in-memory in dev).
  • TTL — 8 hours by default, or matching the Discord token lifetime for OAuth sessions.

Discord OAuth Flow

1. Initiate the flow

GET /auth/discord/start?tenantId=default&mode=redirect&postLoginRedirect=/admin
ParameterRequiredDescription
tenantIdYesTarget tenant (also accepted via x-tenant-id header).
modeNojson (default) returns { authorizationUrl, state }. redirect issues a 302 to Discord.
redirectUriNoOverride the OAuth redirect URI. Falls back to server config.
postLoginRedirectNoRelative path to redirect to after login completes.

JSON response (mode=json):

{
"authorizationUrl": "https://discord.com/oauth2/authorize?client_id=...&state=...",
"state": "opaque-state-token"
}

2. Handle the callback

After the user authorizes, Discord redirects to:

GET /auth/discord/callback?code=AUTHORIZATION_CODE&state=OPAQUE_STATE

The server:

  1. Validates and consumes the one-time state token.
  2. Exchanges the authorization code for Discord tokens.
  3. Creates or retrieves a user and identity record.
  4. Issues a session cookie.
  5. Returns JSON { session } or redirects to postLoginRedirect.

First-user bootstrap: The first Discord user to log in for a tenant is automatically granted the TenantAdmin role.

Local Auth Flow

Login with credentials

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

{
"username": "admin",
"password": "secret",
"tenantId": "default"
}

Response (200):

{
"session": {
"sessionId": "...",
"user": {
"userId": "...",
"tenantId": "default",
"displayName": "...",
"email": "..."
},
"permissionKeys": ["TenantAdmin"]
}
}

The session user includes displayName and email for UI display. The ledgerline_auth cookie is set on the response.

Check Current Session

GET /auth/me
Cookie: ledgerline_auth=...

Response (200):

{
"session": {
"sessionId": "...",
"user": { "userId": "...", "tenantId": "default" },
"permissionKeys": ["TenantAdmin"]
}
}

Returns 401 if no active session.

Switch Tenant

POST /auth/tenant
Content-Type: application/json
Cookie: ledgerline_auth=...

{
"tenantId": "other-tenant"
}

Re-issues the session with permissions for the new tenant.

Logout

POST /auth/logout
Cookie: ledgerline_auth=...

Clears the session cookie and removes the server-side session record.

For redirect-based logout (e.g., from a browser):

GET /auth/logout?redirectTo=/

Error Responses

StatusCondition
400Missing parameters, invalid credentials, provider misconfiguration.
401No active session (for endpoints requiring auth).
404Auth provider not found for the given tenant.