Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Authentication Methods

ahdapa supports several authentication methods for interactive user login. The login page at /ui/auth/login applies them in a fixed priority order: federated identity first, then passkey, then password (static → PAM → LDAP). Only the first matching method is used.


1. Federated identity (upstream IdP redirect)

Ahdapa can delegate authentication to an external OIDC or OAuth2 provider. When a federated IdP is configured, the login page redirects the user there automatically based on their username.

See Federation for setup, FreeIPA auto-discovery, and ACR/AMR override configuration.


2. Passkey (WebAuthn)

After the federated-identity check, if WebAuthn is available in the browser, the login page automatically probes POST /api/auth/passkey/begin for the entered username.

If the user has passkeys enrolled, the browser invokes the platform authenticator (Touch ID, Windows Hello, security key, or phone passkey). On success, a session is issued without the user typing a password. If the user dismisses the authenticator prompt or has no passkeys enrolled, the login page falls through to the password form.

Required configuration:

[ipa]
passkey_rp_id = "idp.example.com"   # must match the domain of the server

passkey_rp_id must be set in [ipa] even when FreeIPA/LDAP is not otherwise used for authentication. Without it, passkey endpoints return 501 Not Implemented and passkey login is silently skipped.

Multi-origin support:

When [server] issuer_aliases is configured (or when IPA auto-derives node aliases from [gssapi] initiator_principal), passkey assertions and registrations are accepted from all configured origins, not just the canonical issuer origin. This means a user can authenticate to https://ipa1.ipa.test/idp with a passkey that was registered against https://ipa-ca.ipa.test/idp, as long as both origins are in the accepted set. The accepted origins are: issuer + all issuer_aliases + auto-derived IPA aliases (node FQDN and ipa-ca.<realm>). No additional configuration is needed for standard IPA deployments.

Passkey self-service enrollment:

Authenticated users can register and delete their own passkeys at /ui/user/profile. The page lists enrolled passkeys by name and registration date and provides a “Register new passkey” button that drives the full WebAuthn attestation flow in the browser.

For FreeIPA/LDAP users, passkey credentials are written to the ipapasskey attribute on the user’s FreeIPA entry using Kerberos S4U2Self impersonation. This makes them visible to any other FreeIPA-aware service (e.g., sssd). For non-IPA users, passkeys are stored in ahdapa’s local database.


3. Password

If neither federated identity nor passkey authentication succeeds, the login page shows a password field. Passwords are verified in order:

  1. Static users — username and password are checked against the [[users]] entries in the configuration file.
  2. PAM (optional, requires --features pam and a [pam] config section) — credentials are passed to the configured PAM service (typically /etc/pam.d/ahdapa). Covers SSSD, winbindd, systemd-homed, and any other PAM-integrated backend.
  3. LDAP simple bind — a bind is attempted against the configured LDAP server as uid=<username> within the discovered domain suffix.

The first backend that returns a definitive answer (authenticated or rejected) wins. PAM and LDAP simple bind are only tried if the previous steps found no match.

Expired passwords (PAM only):

When PAM reports PAM_NEW_AUTHTOK_REQD, the user is redirected to /login/change-password to set a new password before their session is created.

Required configuration (LDAP password):

[ipa]
uri = "ldaps://ipa.example.com"

Required configuration (PAM):

[pam]
service      = "ahdapa"   # /etc/pam.d/ahdapa
timeout_secs = 30

4. OTP (TOTP / HOTP)

Users with OTP tokens enrolled in FreeIPA can authenticate using their password combined with a one-time code. The OTP login stage is reached from the password stage: a link “Sign in with password + OTP code instead” appears below the password form.

The OTP stage shows two separate fields:

  • Password — the user’s regular FreeIPA password.
  • OTP code — the current 6- (or 8-) digit code from the enrolled token, entered separately on screen.

The server concatenates password + otp_code into a single bind credential, opens an anonymous LDAP connection, and calls a simple bind with the OTP_REQUIRED_OID client control (2.16.840.1.113730.3.8.10.7). FreeIPA’s ipa-pwd-extop SLAPI plugin validates the combined credential at bind time — ahdapa never reads the raw OTP secret (ipatokenOTPkey). The control also tells ipa-pwd-extop to reject the bind if no valid OTP code is appended, even if the password alone is correct.

Invalid credentials (LDAP code 49) produce a “Wrong username, password, or OTP code” error. All other LDAP errors are treated as server errors.

Required configuration:

[ipa]
uri = "ldaps://ipa.example.com"

OTP tokens must be enrolled in FreeIPA for the user. They can be enrolled using the FreeIPA CLI (ipa otptoken-add) or via the self-service profile page at /ui/me (see OTP token self-service below).

OTP token self-service

Authenticated users can list, add, and delete their own OTP tokens at /ui/me (the profile page), under the “OTP tokens” section. No administrator action is required.

  • List — shows all enrolled tokens with label, type (TOTP/HOTP), algorithm, digits, period, and status.
  • Add — creates a new TOTP token. The otpauth:// URI is displayed once as a QR code (inline SVG) and as a copyable text string. Close the dialog only after the token has been scanned — the dialog cannot be dismissed any other way, and the secret is not stored by ahdapa.
  • Delete — removes a token by its unique ID. Only tokens owned by the current user can be deleted.

The self-service endpoints require a valid session cookie. The sub from the session identifies the acting user; no privilege escalation is possible.


5. SPNEGO / Kerberos (browser-level)

For domain-joined browsers (Firefox, Chrome on Linux with Kerberos credentials), SPNEGO negotiation happens at the HTTP layer on two routes:

  • GET /ui/auth/login — The login page handler inspects Authorization: Negotiate before serving the SPA HTML. On success it sets a session cookie (ACR urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos, AMR kerberos) and redirects to return_to. On Continue (multi-round GSSAPI exchange) it returns 401. When no valid Negotiate token is present it falls through to the SPA HTML so password login still works.

  • GET/POST /authorize — The authorization endpoint also runs SPNEGO before validating query-string parameters. This enables a single-round-trip OAuth2 flow for curl-style command-line clients:

    • Authorization: Negotiate present + valid OAuth2 params → authenticates, creates a session, and continues the OAuth2 authorization flow in the same request (no redirect-then-retry). The session cookie is appended to the consent redirect response.
    • Authorization: Negotiate present + no OAuth2 params → authenticates, creates a session, returns 400 {"error":"invalid_request","error_description":"client_id required"} with Set-Cookie (no redirect, so curl keeps the cookie for subsequent requests).
    • No Negotiate header → existing session-cookie flow unchanged.
    • Continue → 401 challenge.

This dual-endpoint design is transparent: users visiting the login page are forwarded to their destination automatically if their browser holds a valid Kerberos ticket and the server accepts Negotiate. Command-line clients (curl, httpie) can obtain a session cookie from /authorize without being redirected to the login page.

Service-principal self-registration of OAuth2 clients:

Kerberos service principals (principals whose local part contains /, such as HTTP/client.ipa.test@IPA.TEST) can use the SPNEGO session obtained from /authorize to self-register an OAuth2 client at POST /register without requiring a pre-shared server.registration_token. User principals (no / in the local part, e.g. alice@IPA.TEST) and cross-realm principals are excluded from this path. See Dynamic Client Registration for the full curl workflow and response format.

Required configuration:

[server]
realm = "EXAMPLE.COM"

[gssapi]
service = "HTTP"
keytab  = "/etc/ahdapa/ahdapa.keytab"

Or, using gssproxy instead of a keytab:

[gssapi]
service  = "HTTP"
gssproxy = true

If [gssapi] is absent or the keytab is unreadable at startup, SPNEGO is disabled and a warning is logged. Password and passkey authentication continue to work.



Kerberos client authentication (kerberos_client_auth)

This section covers machine-to-machine OAuth2 client authentication using Kerberos. It is distinct from user-facing SPNEGO (section 5 above): rather than a user proving their identity to obtain a session, a machine proves the identity of the OAuth2 client itself at the token endpoint, replacing a client_secret.

This feature is designed for large-scale SSSD id_provider = idp deployments where every FreeIPA-enrolled machine already holds a Kerberos keytab (host/hostname@REALM) and rotating a shared client_secret across thousands of machines is operationally undesirable.

Prerequisites

  • [ipa] gssapi = true must be set in the server configuration.
  • [gssapi] keytab or [gssapi] gssproxy = true must be configured so the server can accept SPNEGO tokens.
  • The client must be registered via the admin API (not dynamic registration — see below).

Two registration modes

Single-machine client — binds one client ID to one specific host principal:

{
  "client_name": "node1.example.com SSSD client",
  "token_endpoint_auth_method": "kerberos_client_auth",
  "kerberos_principal": "host/node1.example.com@EXAMPLE.COM",
  "scopes": ["openid"]
}

Template client — one client ID covers all machines whose principal matches a glob:

{
  "client_name": "SSSD template client",
  "token_endpoint_auth_method": "kerberos_client_auth",
  "kerberos_principal_pattern": "host/*@EXAMPLE.COM",
  "scopes": ["openid"]
}

The * wildcard in kerberos_principal_pattern matches any sequence of characters except @. At most three wildcards are permitted per pattern.

Exactly one of kerberos_principal or kerberos_principal_pattern must be set. kerberos_client_auth is mutually exclusive with client_secret, jwks_uri, and tls_client_certificate.

HBAC access control (kerberos_hbac_service)

An optional kerberos_hbac_service field names a FreeIPA HBAC service. When set, the server evaluates the replicated IPA HBAC rule set for the authenticated machine principal before issuing a token:

{
  "client_name": "SSSD template client",
  "token_endpoint_auth_method": "kerberos_client_auth",
  "kerberos_principal_pattern": "host/*@EXAMPLE.COM",
  "kerberos_hbac_service": "sssd-idp",
  "scopes": ["openid"]
}

Create the corresponding HBAC service and rules in FreeIPA:

ipa hbacsvc-add sssd-idp --desc "SSSD IdP token endpoint access"
ipa hbacrule-add sssd-idp-allowed --desc "Allow enrolled hosts to get IdP tokens"
ipa hbacrule-add-service sssd-idp-allowed --hbacsvcs=sssd-idp
ipa hbacrule-add-host sssd-idp-allowed --hosts=node1.example.com --hosts=node2.example.com

Known limitation: Rules that match machines by hostgroup membership currently evaluate as deny — individual hostname-based rules work correctly. Use individual host entries in the HBAC rule until hostgroup resolution for machine principals is implemented.

When kerberos_hbac_service is configured and the HBAC rule set is empty (no rules have been mirrored yet), the server denies all tokens (fail-closed) rather than granting open access.

Token flow

The machine presents its Kerberos AP-REQ token in the standard HTTP Negotiate header. kerberos_client_auth is accepted on both the token endpoint (/token) and the device authorization endpoint (/device_authorization), enabling Kerberos-authenticated clients to use either the client credentials flow or the device authorization flow.

Client credentials (machine-only token):

POST /token
Authorization: Negotiate <base64-encoded-AP-REQ>
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=<template_client_id>&scope=openid

Device authorization (machine authenticates the client, user authorises via browser):

POST /device_authorization
Authorization: Negotiate <base64-encoded-AP-REQ>
Content-Type: application/x-www-form-urlencoded

client_id=<template_client_id>&scope=openid+offline_access

The client must have urn:ietf:params:oauth:grant-type:device_code in its grant_types to use the device authorization endpoint.

The server calls try_spnego() to verify the token and extract the authenticated principal. Multi-round GSSAPI exchanges are not supported on these endpoints — the AP-REQ must complete authentication in a single round trip (which is the normal case for host/ service principals).

Token sub for template clients

For single-principal clients, the sub of issued access tokens is the registered client_id.

For template clients (those with kerberos_principal_pattern), the sub is the actual authenticated machine principal (e.g. host/node1.example.com@EXAMPLE.COM) rather than the template client_id. This makes individual machines distinguishable in audit logs and token introspection responses even though they share a single client registration.

Discovery advertisement

kerberos_client_auth is listed in token_endpoint_auth_methods_supported in both /.well-known/oauth-authorization-server and /.well-known/openid-configuration only when [ipa] gssapi = true. When GSSAPI is disabled, the method is absent from discovery so that clients do not attempt to use it.

Dynamic registration exclusion

POST /register rejects "token_endpoint_auth_method": "kerberos_client_auth" with invalid_client_metadata. Kerberos clients must be registered through the admin API (POST /api/admin/clients) by an administrator.

SSSD deployment model

The template client pattern enables a zero-secret SSSD deployment. Every enrolled machine uses the same sssd.conf — no per-machine client secret needs to be generated, distributed, or rotated:

# /etc/sssd/sssd.conf — identical on every enrolled machine
[domain/example.com]
id_provider = idp
idp_client_id = <template_client_id>
# No idp_client_secret — SSSD uses the machine's Kerberos keytab directly

The template client must include directory.read in its allowed scopes so that SSSD can call the identity API (/api/identity/users, /api/identity/groups) after obtaining a token. SSSD uses a two-phase lookup: Phase 1 searches by username or group name; Phase 2 resolves group memberships or group members using the id from Phase 1. See Identity API for the full endpoint reference.

FreeIPA admin workflow for a new deployment:

  1. Register one template client via the admin API with kerberos_principal_pattern = "host/*@EXAMPLE.COM", scopes: ["openid", "directory.read"], and optionally kerberos_hbac_service = "sssd-idp".
  2. In IPA, create HBAC service sssd-idp and create HBAC rules scoped to the relevant hosts or hostgroups.
  3. Deploy sssd.conf with the single idp_client_id across all enrolled machines. No secrets to distribute or rotate.

FreeIPA ipauserauthtype enforcement

When [ipa] gssapi = true, ahdapa reads each user’s ipauserauthtype LDAP attribute during the password (api_auth), OTP, and passkey token flows and applies it as a method gate before proceeding with authentication. The per-user attribute overrides the global default fetched from cn=ipaconfig,cn=etc,<suffix>. An empty effective set means no restriction.

The recognised values are: password, otp, pkinit, hardened, idp, and passkey.

Effect on token flows:

  • If idp is in the effective set, the flow immediately returns a 401 with:

    {
      "error": "federated_login_required",
      "error_description": "This account must authenticate via an external identity provider.",
      "redirect_to": "/auth/external/ipa-google-workspace"
    }
    

    The redirect_to field names the upstream IdP derived from the user’s ipaidpconfiglink attribute. The client or browser should redirect the user to that path to complete authentication via the external IdP.

  • If the attempted method is absent from a non-empty effective set (and idp is not set), the flow returns:

    {
      "error": "invalid_credentials",
      "error_description": "Authentication method not allowed by user policy."
    }
    

The gate is a soft check: if the user entry cannot be fetched from LDAP or the IPA API, the flow proceeds as if there is no restriction (fail-open). The SPNEGO / Kerberos flow is not gated — Kerberos authentication is always permitted when the server has a valid keytab.

The global default auth types are loaded at startup and refreshed every 300 seconds alongside IPA IdP discovery.


Identity HBAC policy enforcement

After a user session is established, Ahdapa evaluates all live Identity HBAC policies before issuing a token. See Identity HBAC Policy for rule structure, axis semantics, and the admin API.


Session lifetime

A successful login by any method issues a session cookie. The session is valid for the duration configured in [tokens]:

KeyDefaultDescription
session_ttl3600 (1 hour)Session cookie lifetime in seconds.

The session cookie is HttpOnly, Secure, and SameSite=Lax. It is used by the admin WebUI and by the consent/device-verification flows.


Rate limiting

Authentication attempts (password, SPNEGO, passkey) are rate-limited per source IP. The default limit is 20 attempts per five-minute rolling window, configurable via server.auth_rate_limit in [server].

Requests exceeding the limit receive 429 Too Many Requests.


Authentication context claims (acr and amr)

Every token ahdapa issues for a user session carries two standard claims that describe how the user authenticated:

  • acr — Authentication Context Class Reference (SAML 2.0 Authentication Context classes, as referenced by OIDC Core §2).
  • amr — Authentication Method Reference (RFC 8176 value strings).
Authentication methodacramr
SPNEGO / Kerberosurn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos["kerberos"]
Password (static, PAM, or LDAP)urn:oasis:names:tc:SAML:2.0:ac:classes:Password["pwd"]
Password + OTP (TOTP/HOTP)urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken["pwd", "otp"]
Passkey / WebAuthnurn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract["hwk"]
Federated upstream IdPForwarded from upstream ID tokenForwarded from upstream ID token

MobileOneFactorContract (SAML AC §3.4) covers authentication with a registered, hardware-bound credential — the appropriate class for FIDO2/WebAuthn passkeys. hwk (RFC 8176 §2) indicates a hardware-protected key.

Machine-to-machine grant types (client_credentials, token_exchange, jwt_bearer, device_code) do not set acr or amr; there is no interactive user session to characterise.

All four user-facing ACR values are advertised in the OIDC discovery document under acr_values_supported. Relying parties that require a minimum assurance level can include acr_values=... in their authorization request; ahdapa will reject the request with access_denied if the authenticated session does not satisfy the requested class.

The values are preserved across token refresh: the acr and amr from the original login session are carried into every renewed access token and ID token until the refresh token family expires.