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:
- Static users — username and password are checked against the
[[users]]entries in the configuration file. - PAM (optional, requires
--features pamand 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. - 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 inspectsAuthorization: Negotiatebefore serving the SPA HTML. On success it sets a session cookie (ACRurn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos, AMRkerberos) and redirects toreturn_to. OnContinue(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: Negotiatepresent + 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: Negotiatepresent + no OAuth2 params → authenticates, creates a session, returns400 {"error":"invalid_request","error_description":"client_id required"}withSet-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 = truemust be set in the server configuration.[gssapi] keytabor[gssapi] gssproxy = truemust 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:
- Register one template client via the admin API with
kerberos_principal_pattern = "host/*@EXAMPLE.COM",scopes: ["openid", "directory.read"], and optionallykerberos_hbac_service = "sssd-idp". - In IPA, create HBAC service
sssd-idpand create HBAC rules scoped to the relevant hosts or hostgroups. - Deploy
sssd.confwith the singleidp_client_idacross 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
idpis in the effective set, the flow immediately returns a401with:{ "error": "federated_login_required", "error_description": "This account must authenticate via an external identity provider.", "redirect_to": "/auth/external/ipa-google-workspace" }The
redirect_tofield names the upstream IdP derived from the user’sipaidpconfiglinkattribute. 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
idpis 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]:
| Key | Default | Description |
|---|---|---|
session_ttl | 3600 (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 method | acr | amr |
|---|---|---|
| SPNEGO / Kerberos | urn: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 / WebAuthn | urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract | ["hwk"] |
| Federated upstream IdP | Forwarded from upstream ID token | Forwarded 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.