Feature Specification: Resource oidc
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/resources/oidc/ and the setup wiring at 093e68b6f, pull requests #1184, #1205 and #1287, and issue #527.
User Scenarios & Testing (mandatory)
The oidc resource gives applications single sign-on, and puts admin UIs without their own login behind forward auth. It wraps the oidc contract. The setups route it to the authelia-oidc provider, which runs Authelia against the ldap resource. The shared resource rules are in the resource family spec.
User Story 1 - Register an application as an OIDC client (Priority: P1)
An application such as Mastodon wants single sign-on. It requests a client with a client ID and its redirect URIs. The routing of the policy selects the provider, and the provider registers the client. The consumer node gets a contract result with the issuer, the endpoints and the client secret file. All of these follow from the issuer. A static client module of the application reads the result. The request holds only data.
Why this priority: users log in to each application with one account. Without this resource, each application needs its own accounts.
Independent Test: unit tests of the wiring of a client: a routed request resolves the contract result on the consumer node, a route to a different provider gives the result of that provider, no route gives no result, and the request is plain data. A unit test of the routing of the setups. A unit test of the policy decisions: which requests are clients, the name of each protected UI, the admin UI filter, and the rules and secrets of the Authelia node. A contract test with a second provider (Dex). No VM test runs Authelia.
Acceptance Scenarios:
- Given a routed client request, When the consumer side is evaluated, Then the contract result holds the issuer, the client secret file, and the authorization, token and userinfo endpoints.
- Given a public client, When the provider is evaluated, Then the client uses PKCE with SHA-256 and no client authentication.
- Given a confidential client, When the provider is evaluated, Then the client authenticates with its secret in the request body.
- Given a client that asks for JWT access tokens, When the provider is evaluated, Then the access tokens are JWTs signed with RSA.
- Given a routing that selects a provider other than
authelia-oidc, When the consumer side is evaluated, Then the contract result holds the issuer and the endpoints of that provider.
User Story 2 - Protect an admin UI with forward auth (Priority: P1)
A component serves an admin UI without its own login. It requests forward auth with a domain and an upstream. The web server of the consumer node asks Authelia about each request. Authelia checks the session and the groups. A user without a session goes to the Authelia login page.
Why this priority: admin UIs give full control of a service. They must not be open to the internet.
Independent Test: unit tests of the consumer side, and a VM test of the UI of lldap.
Acceptance Scenarios:
- Given a forward-auth request, When a request reaches the UI, Then the web server first asks Authelia whether the request is authorized.
- Given an allowed user, When the check passes, Then the upstream gets the user name, the groups, the display name and the email address of the user in request headers.
- Given a user without a session, When Authelia refuses the request, Then the user gets a redirect to the Authelia login page that carries the original URL.
- Given an admin UI in a group that does not deploy admin UIs, When the deployment is evaluated, Then the protection and the UI are not deployed.
- Given a list of allowed groups, When the provider is evaluated, Then the access rule for the domain admits each of these groups.
- Given a location that the request marks as unauthenticated, When a user without a session requests it, Then the request goes to the upstream without the check and with empty identity headers, and the root location keeps the protection.
User Story 3 - Run Authelia against the directory (Priority: P2)
The provider node runs Authelia. Authelia reads its users from the ldap resource. The policy generates the Authelia secrets and declares the TLS domain of Authelia.
Why this priority: the clients and the protections from stories 1 and 2 need a running provider.
Independent Test: unit tests of the grant of the bind credential and of the name of the central OpenBao on the nodes of an operator group. No VM test runs Authelia.
Acceptance Scenarios:
- Given an OpenBao LDAP engine for the directory, When the node is evaluated, Then Authelia binds as a dedicated bind user with the static credential that the engine manages.
- Given no engine, When the node is evaluated, Then Authelia binds with the bind DN of the policy and a password from the secret store of the group.
- Given OpenBao rotates the bind password, When the rendered password changes, Then Authelia restarts and uses the new password.
- Given the
ldapcontract result, When the node is evaluated, Then Authelia connects to the URL of the result. Where the directory serves LDAPS, Authelia verifies the certificate against the system trust, which holds the CA of the private PKI. - Given no mail relay for Authelia, When Authelia sends a notification, Then it writes the notification to a local file.
- Given a group that uses the OpenBao engine of another group, When the node is evaluated, Then the OpenBao agent of the node dials the central OpenBao by the name in the static config of the hosting provider. The domain of the tenant and the scope of the apply do not change this name.
Edge Cases
- When the policy is disabled, it gives nothing. A template that nothing then wires blocks the environment file of the provider.
- A forward-auth request with a subdomain in a policy without a deployment domain fails the evaluation.
- A forward-auth request with neither a subdomain nor a domain fails the evaluation.
- The mail relay is down at start: Authelia starts anyway, because it does not probe the relay at start.
- Many password changes in a short time: rate limits refuse the extra requests.
Requirements (mandatory)
Functional Requirements
- FR-001: The policy MUST give no output when it is disabled.
- FR-002: The policy MUST derive the forward-auth domain from the subdomain and the deployment domain when the request has a subdomain, else take the domain of the request.
- FR-003: The policy MUST deploy a protection only when the request is enabled, and either the group deploys admin UIs or the request is not an admin UI.
- FR-004: The policy MUST generate the Authelia secrets: the JWT secret, the storage encryption key, the OIDC HMAC secret, an RSA 4096 issuer key, and the client secret with its PBKDF2-SHA512 digest. Only the Authelia service MUST be able to read them.
- FR-005: The policy MUST declare the Authelia domain and each deployed forward-auth domain as TLS domains.
- FR-006: A client request MUST default its scopes to
openid,profileandemail, and its authorization policy to one factor. The contract request MUST carry the redirect URIs and the scopes, so that the provider registers them and the client reads them from its contract result. - FR-007: The provider MUST use one factor as its default policy. It MUST store its state in the routed
postgresqldatabase of theautheliacomponent, or in a local database where nopostgresqlprovider resolves. - FR-008: The provider MUST listen on loopback only, or on all addresses when it serves forward auth.
- FR-009: The web server of the provider MUST replace the forwarded-for header with the address of the client, so that a client cannot spoof it.
- FR-010: The provider MUST open HTTP, HTTPS and its own port in the firewall.
- FR-011: A client request MUST contain only data. The consumer side MUST give the contract result as a contract result on the node, not as an argument to a function of the request.
- FR-012: The consumer side MUST want the
oidccontract on the consumer node and MUST import the routing of the policy, so that the contract result of the routed provider resolves on that node. The Authelia node MUST import the same routing. - FR-013: The setups MUST route
oidctoauthelia-oidcwhere the deployment providesoidc, and to no provider where it does not. - FR-014: A forward-auth request MUST contain only data. For each deployed forward-auth request, the consumer side MUST give the node of the UI the served domain, keyed by the upstream. The static module of a UI service MUST set nothing without an entry for its upstream.
- FR-015: The forward-auth web server MUST send each unauthenticated location of the request to the upstream without the check, and MUST clear the identity headers on these locations.
- FR-016: Only the central group (the one group whose roster provides the vars backend) MUST deploy admin UIs.
- FR-017: On the central group, the setups MUST give the Authelia node the LDAP engine of the local OpenBao as its bind engine.
Key Entities
- Client request: client ID, redirect URIs, scopes, authorization policy, whether the client is public, and whether it wants JWT access tokens. It holds only data.
- Contract result: the result on the consumer node for one client ID. It holds the request data and the provider data (the issuer and the endpoints).
- Client module: a static module of an application that reads the contract result and sets nothing without a result for its client ID.
- Forward-auth request: the domain or subdomain, whether it is an admin UI, the upstream, the authorization policy, the allowed groups, websocket support, extra upstream settings, and the unauthenticated locations. It holds only data.
- Served domain entry: the domain of a deployed forward-auth request on the node of the UI, keyed by the upstream.
- UI service module: a static module of a component that runs the UI service of a forward-auth request and sets nothing without an entry for its upstream.
- Policy: enable flag, Authelia domain and port, issuer (by default the Authelia domain over HTTPS), cookie domain, instance name, deployment domain, whether the group deploys admin UIs, and the routing. The routing selects the provider. A routing to a provider other than
authelia-oidcalso imports the module of that provider. authelia-oidcprovider: the Authelia node, with its rate limits and its mail relay.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: the unit tests of the client wiring, the routing, the consumer side and the policy pass.
- SC-002: A user in the Authelia user group logs in to a client application with the directory password.
- SC-003: An anonymous request to a protected admin UI gets a redirect to the Authelia login page.
Assumptions
- The wiring and the setup flow are as in 005. The request and policy model is as in 001.
- The daemon is the
autheliacomponent. The users come from theldapresource. Mail goes through thesmtpresource. The certificate comes from thesslresource. - Authelia sends mail from a
noreplyaddress on its own mail domain. - The name of the central OpenBao comes from the static config of the hosting provider. The mTLS dial verifies the leaf by name, and the leaf has no IP SAN, so the node config needs the name at eval time. The addresses document of the hosting provider gives only addresses, and only at apply time. A read from KV is a bootstrap loop, because the node reads KV through this name. Thus a second hosting provider in this codebase sets its own static config.
- The consumer node evaluates the module of the routed provider to get the contract result. Thus the issuer and the endpoints must follow from the policy at eval time.
- A possible follow-up is that the provider writes its result (the issuer, the endpoints and the client data) to KV, and that the consumer reads it at run time. Then the consumer does not depend on the code of the provider. But this adds a run-time dependency and a bootstrap order. This change is necessary only for a provider whose values are not known at eval time, or for a provider that is not in this repository.
Known Deviations
None.