# Authentication, MFA and OIDC

> Local accounts, sessions, two-factor authentication, re-authentication and single sign-on with an OIDC provider.

## Local accounts

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.

## Sessions

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.

## Two-factor authentication

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.

## Re-authentication for sensitive actions

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`).

## Single sign-on with OIDC

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.

### 1. Register InfraWard in the provider

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).

### 2. Store the client secret

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

```bash
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.

### 3. Configure the API

Add the variables to `api` in `compose.override.yml` (and add the file to `COMPOSE_FILE`, see [Install with Docker Compose](/docs/installation.md#add-settings-not-exposed-in-env)):

```yaml title="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
```

```bash
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. |

### How accounts are created and updated

- 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.

### Disable password login

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

```yaml
      LOCAL_LOGIN: "false"
      LOCAL_LOGIN_EMERGENCY_EMAIL: breakglass@example.com
```

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.

## Troubleshooting sign-in

See [Troubleshooting](/docs/troubleshooting.md#sign-in-and-sessions).

---

HTML page: https://infraward.net/docs/authentication/
