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. In FreeIPA co-deployments, when the built-in allow_all HBAC rule is enabled (the default), HBAC evaluation is skipped entirely. HBAC rules can be managed via the admin WebUI, REST API, or the ahdapactl hbac CLI commands. See Identity HBAC Policy for rule structure, axis semantics, the FreeIPA allow_all integration, and management interfaces.


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.