Skip to content

authentication

See OAuthConfig for details on provider URL, scopes, and field mapping from OIDC claims to ssoossh identity fields (username, groups).

Key Type Default
authentication.client_id string empty
authentication.client_secret string empty
authentication.provider_url string empty
authentication.scopes string profile email
authentication.disable_pkce bool false
authentication.userinfo bool true
authentication.fields.subject string sub
authentication.fields.username string preferred_username
authentication.fields.name string name
authentication.fields.groups string groups
authentication.fields.other_accounts string empty
authentication.fields.service_accounts string empty
authentication.fields.email string email
authentication.fields.extra map

string, default empty

The OAuth2/OIDC client ID registered with the identity provider. Required.

authentication:
client_id: ""

string, default empty

The OAuth2 client secret.

authentication:
client_secret: ""

string, default empty

The OIDC provider’s issuer URL, used for discovery. A trailing /.well-known/openid-configuration is stripped automatically. Required.

authentication:
provider_url: ""

string, default profile email

A space-separated list of additional scopes to request alongside the always-included “openid” scope, e.g. “profile email”.

authentication:
scopes: "profile email"

bool, default false

Drops the PKCE (RFC 7636) code challenge from the authorization request and the code verifier from the token exchange. Off by default: PKCE is what stops an authorization code stolen in transit — out of a proxy log, a browser history, a referrer header — from being redeemed by anyone but the browser that started the login.

It exists because a few providers reject an authorization request carrying code_challenge for a confidential client, and an operator who cannot change that at the provider would otherwise have no way to log in at all. Nothing else in the login changes: state and the ID token nonce are unaffected and still checked.

Turn it on only against a provider that demonstrably refuses the parameters, and treat it as temporary — every provider ssoossh supports otherwise handles PKCE, and the server warns at startup whenever this is set.

authentication:
disable_pkce: false

bool, default true

Calls the provider’s userinfo endpoint at every login (and every claims echo), with the access token the code exchange returned, and fills in the claims the ID token lacks. Many providers keep the ID token lean and put profile, email or groups claims only in userinfo; without this, a fields mapping that names one of them resolves to nothing.

The ID token always wins: a claim it carries is never replaced, and userinfo only adds the keys it lacks. Its sub must equal the ID token’s exactly (OpenID Connect Core 5.3.2); a mismatch fails the login. A provider whose discovery document names no userinfo_endpoint is simply not called.

A failed call (network, an error status, a body that is not JSON) never fails a login by itself: it is logged, and the login carries on with the ID token’s claims. After 10 failed calls in a row, counted across every instance through the database, the server pauses the calls: logins use the ID token alone, and one login every 15 minutes (one across the fleet) makes the call as a probe. A probe that succeeds resumes the calls. Pausing and resuming are each audited (auth.userinfo_paused, auth.userinfo_resumed) and emailed to every admin; the admin diagnostics page shows the state.

Turning this on can add claims the ID token lacked, the configured groups claim included, so a role group can start counting at the next login.

authentication:
userinfo: true

Maps ssoossh identity fields to claim names in the OIDC provider’s ID token (and, with userinfo on, the claims the provider’s userinfo endpoint adds). Username is the only required field; the rest are empty by default, meaning “not populated from OIDC”.

To see which claims a provider actually sends before choosing, run ssoosshd identity echo on the server host and open the one-time link it prints: it needs no admin role and no working mapping. See https://mnestor.github.io/ssoossh/operations/identity-provider/#seeing-what-your-provider-actually-sends

string, default sub

Names the claim holding the unique, immutable account identifier — the value users.subject is keyed by, and the only thing that ties a person to their certificate history across logins.

It defaults to “sub” because that is what OIDC guarantees to be stable per issuer, but not every provider’s “sub” is the identifier an operator wants: Entra ID issues a per-application “sub” and puts the tenant-stable one in “oid”, and a provider fronting an upstream directory may carry the directory’s own UUID in a private claim. Username and email are deliberately not candidates — both change when a person is renamed or married, and keying on either silently forks their history into a second account.

Changing this on a running deployment re-keys every login: users whose row was written under the old claim will no longer match and will be created fresh. Choose it before the first login, not after.

authentication:
fields:
subject: "sub"

string, default preferred_username

Names the claim holding the local account username, e.g. “preferred_username”. Required.

This is the account name on target systems — the default certificate principal — not an identifier. It may change; Subject is what stays put.

authentication:
fields:
username: "preferred_username"

string, default name

Names the claim holding the person’s human-readable name, e.g. “Ada Lovelace”. Display only: it is shown beside the username in the web UI and offered to email templates, and it never becomes a certificate principal, a key ID, or an authorization input.

Empty disables the capture. A configured claim that does not arrive stores empty and is simply not shown; login never fails over one.

authentication:
fields:
name: "name"

string, default groups

Names a claim expected to hold a JSON array of group names. It feeds the certificate lifetime and require-group decision only; group membership is never placed in an issued certificate (see https://mnestor.github.io/ssoossh/internals/invariants/).

authentication:
fields:
groups: "groups"

string, default empty

Names a claim expected to hold a JSON array of alternate account identifiers this identity is known by on target systems (a different username, UPN, or sAMAccountName), added to a certificate’s principal list alongside the username claim.

authentication:
fields:
other_accounts: ""

string, default empty

Names a claim expected to hold a JSON array of service-account identifiers this identity is authorized to manage and enroll certificates for.

authentication:
fields:
service_accounts: ""

string, default email

Names the claim to read the user’s email from. Empty falls back to the standard “email” claim opportunistically (not an error if absent either way).

authentication:
fields:
email: "email"

map

Maps additional template field names to claim names. Each configured claim is captured at login (scalars as strings, string arrays as lists), stored on the users row, and made available to key ID templates as {{.Extra.<name>}} (or {{join .Extra.<name> “<sep>”}} for lists). A claim absent from the ID token stores empty and renders as MISSING; login never fails over one. Names with characters beyond letters/digits/underscores need the template’s index syntax: {{index .Extra “my-name”}}.

For example, capturing a department claim and an account list:

authentication:
fields:
extra:
dept: "https://idp.example.com/department"
accounts: altAccounts

then interpolated into a key ID:

cert_options:
user:
key_id_template: '{{.Username}}-{{.Extra.dept}}-{{join .Extra.accounts ";"}}'

See https://mnestor.github.io/ssoossh/operations/key-id-templates/ for the full template field list and the MISSING rendering rules.