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