Authentication
Set up authentication for your Deckyard installation.
Overview
Section titled “Overview”Authentication is on by default. Deckyard refuses to start while authentication is enabled and AUTH_SECRET is missing, so an instance never runs wide open by accident. To run without authentication on purpose, set AUTH_ENABLED=false (see Disabling Authentication). Sandbox and demo mode are the only other cases where the server starts without a secret.
Authentication supports:
- Database users with password login
- Magic link (passwordless) login via email
- Single sign-on (SSO) with one OpenID Connect (OIDC) provider
- Development bypass mode for local testing

AUTH_SECRET
Section titled “AUTH_SECRET”The secret key used for signing session tokens. This is required when authentication is enabled.
AUTH_SECRET=your-random-secret-string-at-least-32-charactersGenerate a secure secret:
# Using OpenSSLopenssl rand -base64 48
# Using Node.jsnode -e "console.log(require('crypto').randomBytes(48).toString('base64'))"Important:
- The secret must be at least 32 characters; the server refuses to start with a shorter one.
AUTH_ALLOW_WEAK_SECRET=trueoverrides this check, but that is not recommended: rotate to a strong secret instead. - Keep this secret secure and never commit it to version control
- Changing this secret will invalidate all existing sessions
Users are stored in the database; apart from the first admin of a fresh install (see Your first admin), there is no way to define users in environment variables. Admins add, edit and remove users in the admin panel, and can send a new user an invitation email (this needs the email service). Every user has one of two roles: admin or user. See User Management.
AUTH_ADMIN_EMAIL
Section titled “AUTH_ADMIN_EMAIL”Set the administrator email address. The user matching this email automatically receives admin privileges, regardless of their configured role.
AUTH_ADMIN_EMAIL=admin@yourdomain.comIf not set, no user has automatic admin privileges; roles must be assigned explicitly.
Your first admin
Section titled “Your first admin”Every login path needs an existing user, and only an admin can add one. A fresh install therefore creates its first admin from AUTH_ADMIN_EMAIL: when the server starts with authentication on and an empty user table, it adds that address as an admin without a password. The server log names the address and says how to sign in:
- Without SSO: open
/loginand request a magic link, or use “Forgot password” to set a password. Both send an email, so the email service must be configured (see Email Configuration). When SSO is off andBREVO_API_KEYis not set, the log warns that no mail can be sent; configure email or SSO and restart. - With SSO: sign in through the SSO button with that address (see Your first login).
This runs only once. As soon as any user exists, the server creates nothing, so later users are added in the admin panel. It also does nothing when authentication is off (AUTH_ENABLED=false) or the instance runs in sandbox or demo mode, and it skips an address that is not a valid email (the log says so). You do not need to start with AUTH_ENABLED=false to create the first user.
Magic Link Authentication
Section titled “Magic Link Authentication”Passwordless login via email links. Users receive a secure link that logs them in automatically.
Requirements:
- Email service configured (see Email Configuration)
- The user must already have an account; no link is sent to unknown addresses
- Not available when
SSO_ENFORCEis on (see Single Sign-On)
How it works:
- User enters their email address
- System sends a secure, time-limited link
- Clicking the link authenticates the user and creates a session
- Each link works once and expires after 15 minutes
Rate limits:
- 5 link requests per email per hour
- 15 link requests per IP per hour
Password Reset Flows
Section titled “Password Reset Flows”Users with a password can reset it by email.
Requirements:
- Email service configured
- Not available when
SSO_ENFORCEis on
Flow:
- User requests a password reset
- System sends an email with a secure reset link, valid for 1 hour
- User clicks the link and enters a new password (at least 8 characters)
- All existing sessions are invalidated
- User signs in with the new password
Rate limits:
- 3 reset requests per email per hour
- 10 reset requests per IP per hour
Single Sign-On (OIDC)
Section titled “Single Sign-On (OIDC)”Point the whole instance at one OpenID Connect provider, such as Google Workspace, Microsoft Entra ID, Okta, Auth0 or Keycloak. Users click “Sign in with SSO”, authenticate at the provider and land in Deckyard with the same account as a password or magic-link user with that email address.
SSO_ENABLED=trueSSO_PROVIDER=oidcOIDC_ISSUER_URL=https://login.example.comOIDC_CLIENT_ID=...OIDC_CLIENT_SECRET=...OIDC_REDIRECT_URI=https://deck.example.com/api/auth/oidc/callbackOIDC_REDIRECT_URI is <APP_URL>/api/auth/oidc/callback, and must be registered at the provider exactly so. The server refuses to start when SSO_ENABLED=true but one of these settings is missing or a URL is malformed. It warns at startup when OIDC_REDIRECT_URI has another path or another origin than APP_URL, and the warning names the URI to set and register. That is a warning rather than a refusal because a reverse proxy may rewrite the callback; if yours does not, the warning is the bug. oidc is the only supported provider.
Optional settings:
| Variable | Default | Meaning |
|---|---|---|
OIDC_ALLOWED_DOMAINS | (none) | Comma-separated email domains allowed to sign in |
OIDC_AUTO_PROVISION | true | Create unknown users on their first login; false means users must be invited first |
OIDC_DEFAULT_ROLE | user | Role for newly created users: user or admin |
OIDC_ADMIN_GROUPS | (none) | Comma-separated provider group or role values that grant admin; matching ignores case |
OIDC_GROUPS_CLAIM | groups,roles | Comma-separated claims the group or role values are read from; each a claim name or a dot path such as realm_access.roles |
OIDC_ORG_CLAIM | (none) | With several organizations: the claim whose value must match an existing organization’s external ID. A missing claim or an unknown value refuses the login |
OIDC_EXTRA_SCOPES | (none) | Space-separated scopes requested on top of openid email profile, for claims the provider only sends behind a scope of their own. An invalid scope refuses startup |
SSO_ENFORCE | false | true makes SSO the only way in: it hides the password and magic-link forms and refuses password login, magic links and password resets (see Enforcing SSO) |
SSO_BUTTON_LABEL | the translated “Sign in with SSO” | The words on the SSO button, e.g. Sign in with Acme ID; the invite report that tells an inviter how a new member gets in uses the same words |
Enforcing SSO
Section titled “Enforcing SSO”With SSO_ENFORCE=true, the server refuses every local login and recovery request with 403 and { "ok": false, "error": "sso_required" }: password login, magic-link requests, “Forgot password”, and also redeeming a magic link or reset link. The refusal comes before any credential check, token or mail, so a link issued before you switched enforcement on can no longer open a session or reset a password.
Sessions that are already open stay valid; enforcement does not sign anyone out. Enforcement only takes effect while SSO is enabled and its configuration is valid, so a broken SSO setup cannot lock everyone out (the server refuses to start with an enabled but invalid configuration anyway). The development bypass keeps its own development-only rule.
An SSO login can grant admin (through OIDC_ADMIN_GROUPS or AUTH_ADMIN_EMAIL) but never removes it; demoting an admin is done in the admin panel.
Some providers send a claim only when its scope is requested. ZITADEL, for example, puts the user’s organization in urn:zitadel:iam:user:resourceowner:id only with the scope urn:zitadel:iam:user:resourceowner, so set the claim and the scope side by side:
OIDC_GROUPS_CLAIM=urn:zitadel:iam:org:project:rolesOIDC_ORG_CLAIM=urn:zitadel:iam:user:resourceowner:idOIDC_EXTRA_SCOPES=urn:zitadel:iam:user:resourceownerYour first login
Section titled “Your first login”On an instance that signs in through SSO only, the first administrator comes from the provider too:
- Set
AUTH_ADMIN_EMAILto the address your provider account carries, and leaveOIDC_AUTO_PROVISIONattrue. - Register
<APP_URL>/api/auth/oidc/callbackat the provider and set the same value inOIDC_REDIRECT_URI. Start the server, or runnpm run doctor: no warning about the redirect URI means the two agree. - With
OIDC_ORG_CLAIMset, create the organization before anyone signs in, carrying the provider’s organization ID:npm run org:create -- --slug acme --name "Acme" --external-id <provider-org-id>(see Multiple Organizations). Without an organization claim, skip this step. - Open
/loginand choose the SSO button. On a fresh install the server already createdAUTH_ADMIN_EMAILas an admin at startup (see Your first admin), and the login signs you in as that user; otherwise your user is created on the spot. Either way you get the admin role, because the address matchesAUTH_ADMIN_EMAIL. - From here, grant admin through the provider (
OIDC_ADMIN_GROUPS) or the admin panel, and setOIDC_AUTO_PROVISION=falseif people should only get in by invitation.
A refused login returns to /login?error=sso_<reason>, and the reason names the gate: email_unverified, domain_not_allowed, org_claim_missing, org_not_found, not_provisioned, no_membership, state (the ten-minute login cookie expired or was blocked) or token_exchange_failed (client secret, redirect URI or clock; the server log has the provider’s answer).
Seeing what the provider sent
Section titled “Seeing what the provider sent”Which claim carries the groups, and whether the organization claim arrived at all, differs per provider. Instance admins find the claims of the last ten SSO logins of the past 24 hours under Settings → Admin → SSO: claims of recent logins, with time, email and outcome. A refused login is listed too, as long as the token exchange succeeded. Sign in through SSO in a second browser, reload the settings page, and read off the names for OIDC_GROUPS_CLAIM, OIDC_ADMIN_GROUPS and OIDC_ORG_CLAIM.
The list holds claims, never tokens, and lives in memory only: nothing is written to the database, disk or log, and a restart clears it. Organization admins without the instance admin role do not see it.
The sign-in card can carry your own logo too: set APP_LOGO_URL (see Environment).
Development Bypass Mode
Section titled “Development Bypass Mode”For local development, you can bypass authentication entirely.
NODE_ENV=developmentAUTH_DEV_BYPASS=trueWhen enabled:
- All requests are treated as coming from an admin user (
dev@local.test) - No login required
- Full admin privileges granted
The bypass only takes effect when NODE_ENV=development; in any other environment it is ignored.
⚠️ Security Warning: Never enable this in production. With NODE_ENV=production the server refuses to start while AUTH_DEV_BYPASS is on.
Cookie Configuration
Section titled “Cookie Configuration”COOKIE_DOMAIN
Section titled “COOKIE_DOMAIN”Set a cookie domain to share sessions across subdomains.
# Share sessions across all subdomainsCOOKIE_DOMAIN=.example.com
# Single domain onlyCOOKIE_DOMAIN=app.example.comSECURE_COOKIES
Section titled “SECURE_COOKIES”Force secure cookies (HTTPS only). Secure cookies are used automatically when the request arrives over HTTPS, including behind a proxy that sets X-Forwarded-Proto: https.
SECURE_COOKIES=trueSession Behavior
Section titled “Session Behavior”- Sessions are valid for 14 days; this is fixed, not configurable
- Sessions are stored as signed cookies (HttpOnly, SameSite=Lax)
- Password changes invalidate all existing sessions for that user
- Sessions are verified against the database on each request
Disabling Authentication
Section titled “Disabling Authentication”To run Deckyard without authentication (not recommended for production):
AUTH_ENABLED=falseWhen disabled, all users have anonymous admin access. Only an explicit false, 0, no or off disables authentication; an empty or misspelled value leaves it on.
Switching authentication off is not a way to create the first admin: with auth off no user is created. Set AUTH_ADMIN_EMAIL instead (see Your first admin).