Skip to content

Security Profiles and Token Binding

Hans Zandbelt edited this page Aug 8, 2026 · 2 revisions

This page describes the settings that harden the interaction between mod_auth_openidc and the Provider beyond plain OpenID Connect Core: security profiles (OIDCProfile), Pushed Authorization Requests, and the two ways of sender-constraining tokens - DPoP and mutual TLS.

Security Profiles

OIDCProfile selects a coherent set of defaults rather than requiring each individual setting to be turned on by hand. It configures:

  • the Authentication Request method
  • DPoP
  • PKCE
  • ID token aud values
  • token endpoint JWT authentication aud values
  • the iss parameter requirement in authentication responses
  • whether the scope parameter is sent alongside a request_uri in the authentication request
# OIDC10: adheres to the core OpenID Connect spec v1.0 (the default)
# FAPI20: the FAPI 2.0 Security Profile, i.e.:
#         Auth Request Method: PAR, DPoP: Required, PKCE: S256, aud: client_id,
#         token endpoint JWT aud: iss, iss parameter: required,
#         scope: omitted when a request_uri is used
#OIDCProfile [ OIDC10 | FAPI20 ]

A profile sets defaults that individual directives can override, but not for everything: FAPI20 enforces four settings regardless of what is configured, because the profile does not permit them to vary.

setting under FAPI20
authentication request method always PAR, whatever OIDCProviderAuthRequestMethod says
PKCE always S256
token endpoint authentication audience always the provider's issuer
revocation endpoint authentication audience always the provider's issuer

Everything else remains configurable. In particular the acceptable ID token aud values may be overridden, and DPoP stands down to the configured mode when access tokens are certificate-bound and the OP does not advertise DPoP support -- FAPI 2.0 section 5.3.2.1 accepts either mTLS binding or DPoP, so mandating DPoP would break the mTLS variant of the profile.

Pushed Authorization Requests (RFC 9126)

With PAR, the authentication request parameters are POSTed to the Provider over a back channel and the browser is only sent a short request_uri reference, so the parameters cannot be inspected or tampered with in the front channel.

# Send authentication request parameters to the Pushed Authorization Request endpoint
OIDCProviderAuthRequestMethod PAR

# Only needed when OIDCProviderMetadataURL is not used, or the discovered metadata does
# not advertise "pushed_authorization_request_endpoint"
#OIDCProviderPushedAuthorizationRequestEndpoint <url>

OIDCProviderAuthRequestMethod is also settable per Provider through the auth_request_method key in the Provider's .conf file, see Multiple Providers. It is implied by OIDCProfile FAPI20.

Sender-Constrained Tokens

A plain bearer access token can be replayed by whoever obtains it. Both mechanisms below bind the token to a key that the legitimate client holds, so a stolen token on its own is not enough.

DPoP (RFC 9449)

DPoP binds the token to a key held by mod_auth_openidc, proven per request with a signed proof JWT. It requires a signing key, so OIDCPrivateKeyFiles/OIDCPublicKeyFiles must be configured with an RSA or EC private signing key.

# off:      no DPoP token is requested from the OP
# optional: a DPoP token is requested but we continue even if the returned token is Bearer
# required: a DPoP token is requested and we fail if the returned token type is not DPoP
# When not defined "off" is used.
# The 2nd parameter optionally enables the DPoP proof API described below.
#OIDCDPoPMode [off|optional|required] [on|off]

OIDCProfile FAPI20 implies required. The mode can be overridden per Provider with the dpop_mode key in the Provider's .conf file.

DPoP proof API

Applications behind the module that need to use the DPoP-bound access token themselves cannot simply replay it - each call needs a fresh proof signed with the module's key. Enabling the second parameter of OIDCDPoPMode exposes an endpoint that mints one:

<redirect_uri>?dpop=<access_token>&url=<url>[&method=<method>][&nonce=<nonce>]

method defaults to GET. The response is a JSON object holding the proof to send in the DPoP header alongside the access token:

{"DPoP":"eyJ0eXAiOiJkcG9wK2p3dCIs..."}

Caveat: this endpoint hands out a proof for whatever access token is passed to it, so it would undo the proof-of-possession property if it were reachable by the party holding the token. The module therefore rejects any request whose client address is not the server's own local address, and logs a warning telling you to instead expose it through a separate virtual (sub)host that requires client certificate authentication and proxies the request. The check can be disabled by setting the OIDC_DPOP_API_INSECURE environment variable, which - as the name says - you should not do outside of testing.

Mutual TLS (RFC 8705)

RFC 8705 covers two related things that are configured separately: authenticating the client with a TLS certificate, and binding the access token to that certificate.

Client authentication

OIDCProviderTokenEndpointAuth tls_client_auth
# or, for a self-signed client certificate:
#OIDCProviderTokenEndpointAuth self_signed_tls_client_auth

# PEM-formatted client certificate and its private key
OIDCClientTokenEndpointCert <filename>
OIDCClientTokenEndpointKey  <filename>
# Optional password for an encrypted private key; supports "exec:" like OIDCClientSecret
#OIDCClientTokenEndpointKeyPassword [ <passphrase> | "exec:/path/to/otherProgram arg1" ]

These three settings are also available per Provider as token_endpoint_tls_client_cert, token_endpoint_tls_client_key and token_endpoint_tls_client_key_pwd.

Certificate-bound access tokens

# off:  only when one of the mutual-TLS client authentication methods is used, i.e. never
#       inferred from the presence of a client certificate alone
# auto: additionally when a client certificate is configured and the Provider metadata
#       advertises "tls_client_certificate_bound_access_tokens"
# on:   additionally when a client certificate is configured, regardless of what the
#       Provider advertises
# When not defined "auto" is used.
#OIDCCertBoundAccessTokens [off|auto|on]

Two things are easy to get wrong here:

  • The client must be registered with the OP for certificate-bound access tokens. The Provider advertising support says that the OP can bind, not that this client is set up for it.
  • When the Provider publishes mtls_endpoint_aliases, those aliases are used for the back-channel endpoints - except where an endpoint has been configured explicitly with OIDCProviderTokenEndpoint, OIDCProviderUserInfoEndpoint, OIDCProviderRevocationEndpoint or OIDCProviderPushedAuthorizationRequestEndpoint, which always take precedence over the alias.

Per Provider, this is the cert_bound_tokens key.

Under OIDCProfile FAPI20 access tokens are sender-constrained through either mutual TLS or DPoP, so on is implied and DPoP is not required on top of certificate binding.

See also

Clone this wiki locally