Skip to main content
Version: 0.1

Authentication

These routes create and end sessions. They live under /api/auth, not /api/v1, and all of them are anonymous. For how a session or an API key is then used on the rest of the API, see the REST API overview.

Routes​

Method and pathRolePurpose
POST /api/auth/sign-up/emailanonymousCreate the gateway's first account (its admin) and sign it in
POST /api/auth/sign-in/emailanonymousSign in with email and password; sets the session cookie
GET /api/auth/get-sessionanonymousThe current session and user, or null
POST /api/auth/sign-outanonymousEnd the session and clear the cookie
GET /api/auth/idpanonymousThe enabled identity providers, for the login page's buttons
GET /api/auth/idp/{id}/startanonymousRedirect the browser to the provider
GET /api/auth/idp/{id}/callbackanonymousThe provider's return address; signs the user in and redirects to /

A successful sign-in sets the cookie scadex.session. It is HTTP-only, expires 7 days after sign-in (no sliding renewal), and is marked Secure when AUTH_BASE_URL starts with https://. The cookie carries an encrypted session id; the session row in the database is looked up on every request, so a deleted or expired session is refused even if the cookie is still present, and the role in the response is always the role currently stored for the user.

Requests that change state and carry the cookie must also send a trusted Origin header; see the Origin requirement. That includes POST /api/auth/sign-in/email once a cookie exists, and POST /api/auth/sign-out.

Sign up: the first account​

POST /api/auth/sign-up/email
FieldTypeRequiredMeaning
emailstringyesSign-in email; must be unique
namestringyesDisplay name
passwordstringyesStored as a hash

Public sign-up succeeds only while the gateway has no user at all. The account it creates gets the admin role and is signed in at once. The check and the insert share one database transaction, so two callers racing to claim a new gateway cannot both win. After the first account exists, every call answers 403 {"code":"FORBIDDEN","message":"Public sign-up is disabled"}; further users are created by an admin through POST /api/v1/admin/users (see Administration).

Response 200:

{ "token": "<session id>", "user": { "id": "...", "name": "...", "email": "...", "emailVerified": false, "image": null, "role": "admin", "createdAt": "2026-11-08T12:00:00+00:00", "updatedAt": "..." } }

Sign in​

POST /api/auth/sign-in/email
FieldTypeRequiredMeaning
emailstringyes
passwordstringyes
rememberMebooleannoDefault true. false makes the cookie a session cookie that the browser drops when it closes; the 7-day expiry still applies
callbackURLstringnoAccepted for compatibility with the SDK client; the gateway does not act on it

Response 200:

{ "redirect": false, "token": "<session id>", "url": null, "user": { "id": "...", "name": "...", "email": "...", "emailVerified": false, "image": null, "role": "operator", "createdAt": "...", "updatedAt": "..." } }

A wrong email or password answers 401 {"code":"INVALID_EMAIL_OR_PASSWORD","message":"Invalid email or password"}. Only accounts with a password (credential accounts) can sign in here; an account created by an identity provider has none unless an admin set one.

The current session​

GET /api/auth/get-session

Returns null (HTTP 200, body null) when the request carries no valid session. Otherwise:

{
"session": { "id": "...", "expiresAt": "2026-11-15T12:00:00+00:00", "token": "...", "createdAt": "...", "updatedAt": "...", "ipAddress": null, "userAgent": null, "userId": "..." },
"user": { "id": "...", "name": "...", "email": "...", "emailVerified": false, "image": null, "role": "viewer", "createdAt": "...", "updatedAt": "..." }
}

user.role is one of viewer, operator, engineer, admin. Timestamps are ISO-8601 strings. A request authenticated with an API key has no session row, so this route answers null for it.

Sign out​

POST /api/auth/sign-out

Deletes the session row and clears the cookie. Always answers 200 {"success": true}, also when no session was present.

Signing in with an identity provider​

Identity providers are OpenID Connect providers an admin configures (see Identity providers and the admin routes in Administration). The login flow is browser-driven:

  1. GET /api/auth/idp lists the enabled providers as [{ "id": "...", "name": "..." }], sorted by name.
  2. GET /api/auth/idp/{id}/start discovers the provider's configuration from its issuer, generates PKCE and state values, stores them in the cookie scadex.idp (HTTP-only, SameSite=Lax, path /api/auth/idp, 10 minutes) and redirects to the provider's authorization URL with the scopes configured for the provider. The redirect URI is {AUTH_BASE_URL}/api/auth/idp/{id}/callback.
  3. The provider sends the browser to GET /api/auth/idp/{id}/callback?code=&state= (or ?error=). The state cookie is deleted on arrival, so each start allows one attempt. The gateway exchanges the code (with the client secret if one is stored), validates the ID token and its nonce, resolves the account, creates a session, sets scadex.session and redirects to /.

Who the sign-in becomes:

  • A subject that has signed in through this provider before is recognized by the provider's own id for them, not by email.
  • A new subject needs a verified email. If the provider has allowed domains, the email's domain must be one of them.
  • If a user with that email already exists: the sign-in is linked to it only when the provider has Link existing accounts on and the user is not an admin. Otherwise it fails.
  • Otherwise a new user is created with the provider's default role, capped at engineer (an unknown or admin default becomes viewer).

Failures do not return JSON. The callback redirects to /login?error=<code> with one of:

CodeMeaning
provider_unavailableThe provider is unknown, disabled, or its stored secret cannot be read
provider_deniedThe provider returned an error parameter
invalid_stateMissing or expired state cookie, state mismatch, or no code
email_not_verifiedThe provider did not mark the email as verified
domain_not_allowedThe email's domain is not in the provider's allowed list
account_existsA user with that email exists and cannot be linked
sign_in_failedAny other refusal while resolving the account

Other codes come from the OIDC client itself when discovery, the token exchange or the token validation fails.