Skip to content

Authentication, MFA and OIDC

View as Markdown

Local accounts sign in with email and password. Passwords must be between 12 and 1024 characters. Accounts created from Ajustes → Usuarios (Settings → Users) start with a temporary password that is valid for 24 hours (TEMP_PASSWORD_TTL) and must be changed at the first sign-in.

The session lives in the database and the browser only holds an httpOnly cookie named __Host-iw_session (Secure, SameSite=Strict). Every write request also carries a CSRF token in the X-CSRF-Token header.

Setting Default Variable
Absolute session lifetime 12 hours SESSION_TTL (300 s to 7 days)
Idle timeout 2 hours SESSION_IDLE_TTL
Login attempts per IP 5 per minute, then blocked for a minute LOGIN_THROTTLE_LIMIT, LOGIN_THROTTLE_TTL
Failed logins per account, from any IP 10 per 5 minutes LOGIN_ACCOUNT_THROTTLE_LIMIT, LOGIN_ACCOUNT_THROTTLE_TTL

The per-account limit does not block networks from which the account signed in successfully in the last 30 days, so an attacker cannot lock the owner out.

Changing your password closes your other sessions and revokes your API tokens. When an administrator resets a user’s password, that user’s sessions and API tokens end too.

InfraWard supports TOTP (RFC 6238) with any authenticator app.

  • Administrators who sign in with a password must enable it. The first sign-in opens the setup screen and nothing else is reachable until it is done.
  • Other roles can enable it from Ajustes → Perfil → Seguridad (Settings → Profile → Security).
  • Setup shows ten single-use recovery codes. The profile page shows how many are left and can generate a new set.
  • An administrator can reset another user’s second factor from Ajustes → Usuarios. That also closes the user’s sessions.

TOTP and recovery codes share the login rate limit, applied per user.

Some actions ask you to confirm your identity again when the last confirmation is older than 10 minutes (REAUTH_MAX_AGE, 60 to 3600 seconds): purging assets, changing roles, deactivating users, creating write tokens, revealing a vault password, granting terminal access, opening a terminal and changing alert channels. Users with TOTP confirm with a code; OIDC users are sent to the provider again, which must ask for their credentials (InfraWard sends prompt=login and max_age=30).

InfraWard works with any OpenID Connect provider: Keycloak, Authentik, Microsoft Entra ID, Google, Zitadel and others. Setting OIDC_ISSUER enables it and adds a button to the login page.

Create a confidential client (or a public client with PKCE) with:

  • Redirect URI: https://infraward.example.com/api/auth/oidc/callback
  • Scopes: openid email profile, plus whatever your provider needs to include group membership in the ID token or userinfo.
  • A claim that lists the user’s groups or roles (for example groups, or realm_access.roles in Keycloak).

The client secret never goes in an environment variable. Store it in the secrets provider and pass a reference. With the API running:

Terminal window
printf '{"client_secret":"PASTE-THE-SECRET-HERE"}' \
| docker compose exec -T api node dist/cli/secret-put.js secret://openbao/oidc/sso

The JSON is read from standard input, so the secret does not appear in the process list or the shell history (the printf line does, so prefer reading it from a file or a password manager). Without OpenBao, use secret://local/oidc/sso instead.

For a public client with PKCE, skip this step and leave OIDC_CLIENT_SECRET_REF unset.

Add the variables to api in compose.override.yml (and add the file to COMPOSE_FILE, see Install with Docker Compose):

compose.override.yml
services:
api:
environment:
OIDC_ISSUER: https://sso.example.com/realms/main
OIDC_CLIENT_ID: infraward
OIDC_CLIENT_SECRET_REF: secret://openbao/oidc/sso#client_secret
OIDC_REDIRECT_URI: https://infraward.example.com/api/auth/oidc/callback
OIDC_PROVIDER_NAME: Company SSO
OIDC_GROUPS_CLAIM: groups
OIDC_ROLE_MAPPING: infraward-admins=admin,infraward-editors=editor,infraward-ops=operator,infraward-audit=auditor,infraward-users=viewer
OIDC_UNMAPPED: deny
Terminal window
docker compose up -d api
Variable Default Meaning
OIDC_ISSUER unset Issuer URL. Enables OIDC. Must be https in production.
OIDC_CLIENT_ID unset Client ID. Required with OIDC_ISSUER.
OIDC_CLIENT_SECRET_REF unset secret://…#field reference to the client secret. Unset means a public client with PKCE.
OIDC_REDIRECT_URI unset Must be <public URL>/api/auth/oidc/callback.
OIDC_TOKEN_AUTH_METHOD client_secret_basic Or client_secret_post.
OIDC_PROVIDER_NAME OIDC Text of the login button.
OIDC_SCOPES openid email profile Must include openid.
OIDC_GROUPS_CLAIM groups Claim with the groups. Dotted paths are accepted, such as realm_access.roles.
OIDC_ROLE_MAPPING unset group=role pairs separated by commas. With several matching groups, the role with the most permissions wins.
OIDC_UNMAPPED deny What happens to a user with no mapped group: deny rejects the login, viewer lets them in read-only.
OIDC_ALLOW_UNVERIFIED_SIGNUP false Create accounts even when the provider does not mark the email as verified. See below.
  • The first OIDC sign-in creates the user automatically with the mapped role, but only if the provider marks the email as verified (email_verified: true). Otherwise the sign-in is rejected and no account is created: someone who registered in the provider with another person’s email would take over that address in InfraWard.
  • Microsoft Entra ID and other providers that verify emails themselves but do not send email_verified need OIDC_ALLOW_UNVERIFIED_SIGNUP: "true". Without it, new users cannot sign in. Enable it only if every account in the provider has an email it has verified.
  • The role is recalculated from the groups at every sign-in, so role changes are made in the provider.
  • An existing local account is linked to the OIDC identity when the provider reports the same email as verified. OIDC_ALLOW_UNVERIFIED_SIGNUP never changes this.
  • With OIDC enabled, users cannot change their own email; an administrator does it.

When everyone signs in through the provider, turn off password login and keep one local account for emergencies:

LOCAL_LOGIN: "false"
LOCAL_LOGIN_EMERGENCY_EMAIL: [email protected]

With LOCAL_LOGIN=false only the emergency account can use a password. Store its password and recovery codes offline: it is the way in if the provider is down.

See Troubleshooting.