Configuration
This authorization flow is in preview and requires the Advanced or Customization section of the Console Project configuration.
In order to implement both cookie-based and token-exchange patterns, two main components are involved:
- The BFF
- The API Gateway
The BFF (authtool_bff) handles:
- Session management
- Authorization code flow with AS
- Secure storage of tokens
- OIDC logout flow
The API Gateway (envoy) handles:
- Token validation
- Cookie <-> Token exchange
Envoy API Gateway
To set up filters in Envoy, refer to on-request-scripts.yaml
and http-filters.yaml
in the Console Project advanced configuration for API Gateway Envoy.
The Lua filter that exchanges the token can be downloaded from Mia CDN or by specific version:
# available versions are
# -> latest
# -> 0.1.0 (=latest)
version=0.1.0
curl -o token-exchange.lua "https://cdn.mia-platform.eu/runtime/platform/auth/authtool/token-exchange/${version}/init.lua"
Mount the script on the envoy microservice as a configmap on path /etc/lua/lib as
a file named token-exchange.lua.
Then update on-request-scripts.yaml with:
- listener_name: frontend
body: |-
package.path = '/etc/lua/lib/?.lua;' .. package.path
local token_exchange = require('token-exchange')
-- Configuration
local config = {
token_service_cluster = "authtool-bff",
token_service_endpoint = "/bff/token",
session_cookie_name = "__Host-session_id",
}
token_exchange.on_request(request_handle, config)
Keep in mind that:
authtool-bffis the name of the microservice associated with theauthtool_bffconfidential client in your project- The
authtool_bffAPI is served under a/bff/path prefix - The cookie name must match your
sessionIdHardeningsetting (here,__Host-session_id)
Token validation is handled by the JwtAuthentication filter, which verifies JWT signatures against your AS / OpenID Provider public key or JWK Set.
Add a filter in http-filters.yaml as in the following non-normative example:
- listener_name: frontend
order: 65
name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
"<provider_name>":
issuer: "<issuer_url>"
audiences: [
# your token audiences
]
remote_jwks:
http_uri:
uri: "<authorization_server_uri>"
cluster: "<authorization_server_cluster>"
timeout: 1s
#
# refer to envoy documentation to
# decide how to handle to token after validation,
# for instance:
#
forward: false
forward_payload_header: x-jwt-payload
#
rules: [
# add rules to match
# the part of the API
# that must be Authorized by
# a valid token Authorization header
#
# for instance:
#
- match:
prefix: "/api"
requires:
provider_name: "<provider_name>"
]
Finally, add the issuer as a cluster in clusters.yaml as in
the following non-normative example:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: "<authorization_server_cluster>"
connect_timeout: 30s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: "<authorization_server_cluster>"
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: "<address>"
port_value: 443
#
# handle TLS here:
#
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
validation_context:
trusted_ca:
filename: /etc/ssl/certs/ca-certificates.crt
BFF
The BFF is implemented by a service called authtool_bff, available as an OCI image:
<image_registry_domain>/platform/auth/authtool/authtool_bff:<tag>
The full configuration schema is available on Mia CDN and rendered below:
- Schema Viewer
- Raw JSON Schema
- Example
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Configuration",
"type": "object",
"properties": {
"client": {
"$ref": "#/definitions/AuthorizationClientSettings"
},
"connections": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Connection"
}
},
"server": {
"allOf": [
{
"$ref": "#/definitions/RestServerSettings"
}
],
"default": {
"apiPrefix": "/",
"ip": "0.0.0.0",
"port": 3000
}
},
"session": {
"$ref": "#/definitions/SessionSettings"
}
},
"required": [
"client",
"session"
],
"definitions": {
"Algorithm": {
"description": "The algorithms supported for signing/verifying JWTs",
"oneOf": [
{
"description": "HMAC using SHA-256",
"type": "string",
"const": "HS256"
},
{
"description": "HMAC using SHA-384",
"type": "string",
"const": "HS384"
},
{
"description": "HMAC using SHA-512",
"type": "string",
"const": "HS512"
},
{
"description": "ECDSA using SHA-256",
"type": "string",
"const": "ES256"
},
{
"description": "ECDSA using SHA-384",
"type": "string",
"const": "ES384"
},
{
"description": "ECDSA using SHA-512",
"type": "string",
"const": "ES512"
},
{
"description": "RSASSA-PKCS1-v1_5 using SHA-256",
"type": "string",
"const": "RS256"
},
{
"description": "RSASSA-PKCS1-v1_5 using SHA-384",
"type": "string",
"const": "RS384"
},
{
"description": "RSASSA-PKCS1-v1_5 using SHA-512",
"type": "string",
"const": "RS512"
},
{
"description": "RSASSA-PSS using SHA-256",
"type": "string",
"const": "PS256"
},
{
"description": "RSASSA-PSS using SHA-384",
"type": "string",
"const": "PS384"
},
{
"description": "RSASSA-PSS using SHA-512",
"type": "string",
"const": "PS512"
},
{
"description": "Edwards-curve Digital Signature Algorithm (EdDSA)",
"type": "string",
"const": "EdDSA"
}
]
},
"ApiPrefix": {
"title": "string",
"type": "string",
"pattern": "^/(?:[a-zA-Z0-9._~-]+|\\{[a-zA-Z0-9._~-]+\\})(?:/(?:[a-zA-Z0-9._~-]+|\\{[a-zA-Z0-9._~-]+\\}))*$"
},
"AuthorizationClientSettings": {
"type": "object",
"properties": {
"clientId": {
"description": "The client identifier issued to the client during the registration process",
"allOf": [
{
"$ref": "#/definitions/Secret"
}
]
},
"credentials": {
"description": "Client credentials used for token retrieval,\nthey can be absent for public clients if\nthe client application is registered as such\nand the IdP supports it.",
"allOf": [
{
"$ref": "#/definitions/Credentials"
}
]
},
"extra": {
"description": "Extra parameters to add to the authorization request.\nThese parameters will be added to the authorization URL query string.\n\nFor instance `auth0` requires the `audience` parameter to be set\nto obtain a decrypted access token.",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "string"
}
},
"idTokenValidationIssuer": {
"description": "ID Token issuer validation strategy.\nIf not set, the default behavior is to validate\nagainst the `issuer` value.\n\nThis is useful for identity providers which\nissue ID Tokens with a different issuer\nthan the one defined in the metadata document.",
"type": [
"string",
"null"
]
},
"issuer": {
"description": "The issuer URL of the authorization server",
"type": "string",
"format": "uri"
},
"issuerMetadataSegment": {
"description": "The metadata segment to append to the issuer URL\nto retrieve the OpenID Connect metadata or OAuth Server Metadata.\n\nDefaults to `/.well-known/openid-configuration`",
"allOf": [
{
"$ref": "#/definitions/ApiPrefix"
}
],
"default": "/.well-known/openid-configuration"
},
"postLogoutRedirectUri": {
"description": "URI to which the RP is requesting that the End-User's User Agent\nbe redirected after a logout has been performed.\nThis URI SHOULD use the https scheme and MAY contain port,\npath, and query parameter components;\nhowever, it MAY use the http scheme, provided that the Client Type is confidential,\nas defined in Section 2.1 of OAuth 2.0 [RFC6749], and provided the OP allows the use of http RP URIs.\n\nThe value MUST have been previously registered with the OP,\neither using the post_logout_redirect_uris Registration parameter or via another mechanism.",
"anyOf": [
{
"$ref": "#/definitions/UrlAsciiChecked"
},
{
"type": "null"
}
]
},
"preferredTokenEndpointAuthMethod": {
"description": "Preferred token endpoint authentication method.\nIf not set, the default method defined by the\nauthorization server will be used.\n\nThis is useful for `okta` identity providers\nwhich require `client_secret_post` method but still\nclaim to support `client_secret_basic` as default.",
"anyOf": [
{
"$ref": "#/definitions/TokenAuthMethod"
},
{
"type": "null"
}
]
},
"preferredTokenEndpointSigningAlgorithm": {
"description": "Preferred signing algorithm for token endpoint.\nIt is meaningful only when the token auth\nmethod is [`TokenAuthMethod::PrivateKeyJWT`].\nIf not set, the credentials private key algorithm and\nthe authorization server metadata establish the default to be used.",
"anyOf": [
{
"$ref": "#/definitions/Algorithm"
},
{
"type": "null"
}
]
},
"redirect_uri": {
"description": "The redirection URI registered by the client application",
"allOf": [
{
"$ref": "#/definitions/UrlAsciiChecked"
}
]
},
"scope": {
"description": "The set of scopes requested by the client application",
"allOf": [
{
"$ref": "#/definitions/Scope"
}
],
"default": ""
}
},
"required": [
"issuer",
"clientId",
"redirect_uri",
"credentials"
]
},
"Connection": {
"oneOf": [
{
"type": "object",
"properties": {
"config": {
"$ref": "#/definitions/RedisConfig"
},
"type": {
"type": "string",
"const": "redis"
}
},
"required": [
"type",
"config"
]
}
]
},
"ConnectionOrRef": {
"anyOf": [
{
"type": "object",
"properties": {
"connectionName": {
"type": "string"
}
},
"required": [
"connectionName"
]
},
{
"$ref": "#/definitions/RedisConfig"
}
]
},
"CookieHardeningRule": {
"description": "Cookie hardening rules for the session ID cookie.",
"oneOf": [
{
"description": "Prefix the cookie name with \"__Host-\" to enforce\n[cookie hardening rules](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis)\nwhich entail:\n- served over HTTPS\n- Secure\n- no Domain attribute\n- Path=/",
"type": "string",
"const": "host"
},
{
"description": "Prefix the cookie name with \"__Secure-\" to enforce\n[cookie hardening rules](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis)\nwhich entail:\n- served over HTTPS\n- Secure",
"type": "string",
"const": "secure"
},
{
"description": "No prefix",
"type": "string",
"const": "none"
}
]
},
"CookieMode": {
"description": "Sets the behavior of the callback endpoint.\n\nThis modes are regulated by\n[browser-based applications security considerations](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps),\nand [Best Current Practice for Securing Browser-Based Applications](https://www.rfc-editor.org/rfc/rfc9207).\n\nWhen attaching cookies to requests, consider that all modes by default\napplies the following security measures:\n\nThe following cookie security guidelines are relevant for this particular BFF architecture:\n\n- The BFF MUST enable the Secure flag for its cookies\n- The BFF MUST enable the HttpOnly flag for its cookies\n- The BFF SHOULD enable the SameSite=Strict flag for its cookies\n- The BFF SHOULD set its cookie path to /\n- The BFF SHOULD NOT set the Domain attribute for cookies\n- The BFF SHOULD start the name of its cookies with the __Host prefix ([I-D.ietf-httpbis-rfc6265bis])",
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bff"
}
},
"allOf": [
{
"$ref": "#/definitions/CookieSettings"
}
],
"required": [
"type"
]
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "tokenMediation"
}
},
"allOf": [
{
"$ref": "#/definitions/CookieSettings"
}
],
"required": [
"type"
]
}
]
},
"CookieSameSite": {
"description": "`SameSite` attribute applied to the session cookie.",
"oneOf": [
{
"description": "SameSite=Strict",
"type": "string",
"const": "strict"
},
{
"description": "SameSite=Lax",
"type": "string",
"const": "lax"
},
{
"description": "SameSite=None and will force cookie to set attribute `Secure`",
"type": "string",
"const": "none"
}
]
},
"CookieSettings": {
"description": "Settings for the session cookie.",
"type": "object",
"properties": {
"domain": {
"description": "`Domain` attribute applied to the session cookie. It is overridden\nby setting cookie hardening to `Host`",
"type": [
"string",
"null"
],
"default": null
},
"expirationSecs": {
"description": "Sets the expiration time for cookies in seconds",
"type": [
"integer",
"null"
],
"format": "int64"
},
"http_only": {
"description": "`HttpOnly` attribute applied to the session cookie.\nThis is not available in the release build.",
"type": "boolean",
"default": true
},
"partitioned": {
"description": "`Partitioned` attribute applied to the session cookie.",
"type": "boolean",
"default": false
},
"path": {
"description": "`Path` attribute applied to the session cookie. It is overridden\nby setting cookie hardening to `Host`",
"type": [
"string",
"null"
],
"default": null
},
"sameSite": {
"description": "`SameSite` attribute applied to the session cookie.",
"allOf": [
{
"$ref": "#/definitions/CookieSameSite"
}
]
},
"secure": {
"description": "`Secure` attribute applied to the session cookie.\nThis property is not used when session hardening is enabled",
"type": "boolean",
"default": true
},
"sessionIdHardening": {
"description": "Session hardening prefix",
"allOf": [
{
"$ref": "#/definitions/CookieHardeningRule"
}
]
}
}
},
"Credentials": {
"type": "object",
"properties": {
"kid": {
"type": [
"string",
"null"
]
}
},
"oneOf": [
{
"type": "object",
"properties": {
"secret": {
"$ref": "#/definitions/Secret"
}
},
"required": [
"secret"
]
},
{
"type": "object",
"properties": {
"privateKey": {
"$ref": "#/definitions/Secret"
}
},
"required": [
"privateKey"
]
}
]
},
"ProtocolVersion": {
"description": "Enum representing the communication protocol with the server.\n\nThis enum represents the types of data that the server can send to the client,\nand the capabilities that the client can use.",
"oneOf": [
{
"description": "<https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md>",
"type": "string",
"const": "RESP2"
},
{
"description": "<https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md>",
"type": "string",
"const": "RESP3"
}
]
},
"RedisConfig": {
"type": "object",
"properties": {
"connection": {
"description": "Connection settings",
"allOf": [
{
"$ref": "#/definitions/RedisConnectionInfo"
}
]
},
"host": {
"description": "Hostname",
"type": "string"
},
"namespace": {
"description": "Base namespace prefix for this client",
"type": [
"string",
"null"
]
},
"port": {
"description": "Port",
"type": "integer",
"format": "uint16",
"default": 6379,
"maximum": 65535,
"minimum": 0
},
"tls": {
"description": "TLS settings",
"anyOf": [
{
"$ref": "#/definitions/RedisConfigTls"
},
{
"type": "null"
}
]
}
},
"required": [
"host"
]
},
"RedisConfigTls": {
"type": "object"
},
"RedisConnectionInfo": {
"description": "Redis specific/connection independent information used to establish a connection to redis.",
"type": "object",
"properties": {
"db": {
"description": "The database number to use. This is usually `0`.",
"type": "integer",
"format": "int64",
"default": 0
},
"password": {
"description": "Optionally a password that should be used for connection.",
"anyOf": [
{
"$ref": "#/definitions/Secret"
},
{
"type": "null"
}
]
},
"protocol": {
"description": "Version of the protocol to use.",
"allOf": [
{
"$ref": "#/definitions/ProtocolVersion"
}
]
},
"username": {
"description": "Optionally a username that should be used for connection.",
"type": [
"string",
"null"
]
}
},
"required": [
"protocol"
]
},
"RestServerSettings": {
"type": "object",
"properties": {
"apiPrefix": {
"allOf": [
{
"$ref": "#/definitions/ApiPrefix"
}
],
"default": "/"
},
"ip": {
"description": "Server bind IP",
"type": "string",
"format": "ipv4",
"default": "0.0.0.0"
},
"port": {
"description": "gRPC Server port",
"type": "integer",
"format": "uint16",
"default": 3000,
"maximum": 65535,
"minimum": 0
}
}
},
"Scope": {
"description": "a space separated list of scopes: allowed characters are ASCII 0x21 / 0x23-0x5B / 0x5D-0x7E",
"type": "string"
},
"Secret": {
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"encoding": {
"description": "Define which type of encoding the library supports when it needs to read the actual secret value.",
"type": "string",
"enum": [
"base64"
]
},
"key": {
"type": "string"
},
"type": {
"const": "env"
}
},
"required": [
"type",
"key"
]
},
{
"type": "object",
"properties": {
"encoding": {
"description": "Define which type of encoding the library supports when it needs to read the actual secret value.",
"type": "string",
"enum": [
"base64"
]
},
"key": {
"type": "string"
},
"path": {
"type": "string"
},
"type": {
"const": "file"
}
},
"required": [
"type",
"path"
]
}
],
"examples": [
"my-secret",
{
"key": "CUSTOM_ENV_VAR",
"type": "env"
},
{
"encoding": "base64",
"key": "CUSTOM_ENV_VAR",
"type": "env"
},
{
"path": "/path/to/file",
"type": "file"
}
]
},
"SessionSettings": {
"type": "object",
"properties": {
"authorizationFlowCache": {
"description": "Cache layer for authorization requests",
"allOf": [
{
"$ref": "#/definitions/ConnectionOrRef"
}
]
},
"disableLogoutOnGet": {
"description": "Allow disabling the logout on GET.\nThis is useful on SameSite=Lax cookies to\nprevent a CSRF request on logout.",
"type": "boolean",
"default": false
},
"encryptAccessToken": {
"description": "Should encrypt access_token at rest (default: true).\nIf the token has an expiration, maybe a short one,\nto make the token retrieval faster, encryption can be\ndeactivated. Be careful with this option.",
"type": "boolean",
"default": true
},
"encryptionKey": {
"description": "Encryption key for refresh tokens and access tokens at rest.",
"allOf": [
{
"$ref": "#/definitions/Secret"
}
]
},
"errorRedirects": {
"description": "Optional URL to redirect users in case of errors",
"anyOf": [
{
"$ref": "#/definitions/UrlAsciiChecked"
},
{
"type": "null"
}
]
},
"mode": {
"description": "Sets the behavior of the callback endpoint.",
"allOf": [
{
"$ref": "#/definitions/CookieMode"
}
]
},
"refreshTokenExpirationPayloadField": {
"description": "Access Token response key containing expiration\nof the refresh token. This key is not mandated\nby OAuth 2.0 spec.",
"type": [
"string",
"null"
]
},
"refreshTokenExpirationSecs": {
"description": "Sets the expiration time for refresh tokens in seconds\n(default: 7 days). This value cannot be inferred from\naccess token response, so it must be set here.",
"type": "integer",
"format": "int64",
"default": 604800
},
"refreshTokenRotation": {
"description": "Enable refresh token rotation (default: false).\n\nThe IdP must support it and the client application\nmust be registered to use it. If enabled, every time\na refresh token is used to obtain new tokens, a new\nrefresh token is also issued and the previous one\nis invalidated.",
"type": "boolean",
"default": false
},
"sessionJitterSecs": {
"description": "Adds a jitter in seconds to the session expiration time\nto avoid tokens that expire during transit\n(default: 7 seconds).",
"type": "integer",
"format": "int64",
"default": 7
},
"signingKey": {
"description": "Key to sign cookies",
"allOf": [
{
"$ref": "#/definitions/Secret"
}
]
},
"userClaims": {
"description": "Id token claims to be included in the user info stored in the session.",
"type": "array",
"default": [
"email"
],
"items": {
"type": "string"
},
"uniqueItems": true
}
},
"required": [
"signingKey",
"authorizationFlowCache",
"encryptionKey"
]
},
"TokenAuthMethod": {
"type": "string",
"enum": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt",
"none"
]
},
"UrlAsciiChecked": {
"type": "string",
"format": "uri"
}
}
}
{
"$schema": "https://cdn.mia-platform.eu/runtime/platform/auth/authtool/authtool-bff/0.2.1/authtool_bff.schema.json",
"connections": {
"authorization-cache": {
"type": "redis",
"config": {
"host": "authorization-cache"
}
}
},
"client": {
"issuer": "https://keycloak.mia-demo-re5gu6.gcp.mia-platform.eu/realms/platform-demo",
"issuerMetadataSegment": "/realms/platform-demo/.well-known/openid-configuration",
"clientId": "catalog-website",
"redirect_uri": "https://{{PROJECT_HOST}}/bff/oauth/callback",
"scope": "openid email profile catalog-mcp",
"credentials": {
"privateKey": {
"type": "file",
"path": "/run/secrets/authtool-bff-keys/key.pem"
}
},
"preferredTokenEndpointAuthMethod": "private_key_jwt",
"postLogoutRedirectUri": "https://catalog.mia-demo-re5gu6.gcp.mia-platform.eu/bff/oidc/logout/callback"
},
"session": {
"signingKey": {
"type": "file",
"path": "/run/secrets/authtool-bff-keys/cookie-secret.key"
},
"authorizationFlowCache": {
"connectionName": "authorization-cache"
},
"encryptionKey": {
"type": "file",
"path": "/run/secrets/authtool-bff-keys/redis-token-enc.key"
},
"refreshTokenExpirationPayloadField": "refresh_expires_in"
},
"server": {
"apiPrefix": "/bff"
}
}
Architecturally, authtool_bff interacts with four components:
- Session — browser cookie
- Cache — token storage (Redis)
- Client — HTTP calls to the Authorization Server
- REST API server — exposes endpoints to the frontend
Session Management
The session configuration block controls how authtool-bff creates, secures, and maintains the cookie-based session with the browser.
Cookie Creation
When the OAuth2 callback completes, the BFF sets a session cookie. The mode property determines how:
bff— the cookie carries only an opaque session ID; tokens never reach the browsertokenMediation(default) — the cookie still maintains the session, but a short-lived access token can be returned to JavaScript
Cookie attributes are configured under mode:
{
"session": {
"mode": {
"type": "bff",
"sessionIdHardening": "host",
"sameSite": "strict",
"secure": true,
"http_only": true,
"path": "/",
"domain": null,
"partitioned": false,
"expirationSecs": 3600
}
}
}
This produces a cookie like:
Set-Cookie: __Host-session_id=<opaque>; HttpOnly; Secure; SameSite=Strict; Path=/
Cookie Hardening
The sessionIdHardening field controls the cookie name prefix (defaults to host):
| Value | Cookie name prefix | Effect |
|---|---|---|
host | __Host- | Forces Secure, Path=/, no Domain (default) |
secure | __Secure- | Forces Secure |
none | (no prefix) | No enforcement, useful for local development over HTTP |
The http_only cookie attribute defaults to true but can only be set to false in debug builds.
In release builds it is always enforced to true.
Cookie Signing and Token Encryption
Two keys protect session integrity and token storage:
{
"session": {
"signingKey": { "type": "env", "key": "SESSION_SIGNING_KEY" },
"encryptionKey": { "type": "env", "key": "SESSION_ENCRYPTION_KEY" },
"encryptAccessToken": true
}
}
signingKey— signs the cookie value so the BFF can detect tamperingencryptionKey— encrypts both access tokens and refresh tokens at rest in RedisencryptAccessToken— can be set tofalseto skip AT encryption for performance when tokens are short-lived (default:true)
Token Storage and Expiration
The BFF stores tokens in Redis and controls their lifecycle. Three distinct time boundaries govern how long a user stays authenticated; each maps to a BFF or Authorization Server (AS) setting:
| # | Time boundary | Where it is set | BFF configuration field | Typical value |
|---|---|---|---|---|
| 1 | Access Token (AT) expiration | AS client settings | — (no BFF config needed) | 5 – 15 min |
| 2 | Refresh Token (RT) idle / max time (SSO idle timeout) | AS client settings | refreshTokenExpirationSecs or refreshTokenExpirationPayloadField | hours to days |
| 3 | Cookie max time (SSO session max) | BFF + AS client settings | mode.expirationSecs | hours to days |
These values must be aligned with the corresponding settings on the Authorization Server client registration. A mismatch (e.g. a cookie that outlives the RT) will cause silent session failures.
1. Access Token Expiration
The AT lifetime is controlled entirely by the Authorization Server.
The BFF honours whatever exp claim the AS puts in the token — no additional configuration is required.
The only related BFF setting is sessionJitterSecs (default: 7), which subtracts a few seconds from the AT expiry so that the BFF refreshes the token before it expires mid-flight.
2. Refresh Token Expiration
The RT determines how long the BFF can silently renew access tokens on behalf of the user. This corresponds to the SSO idle timeout (or SSO idle max) on the AS.
The BFF needs to know the RT lifetime so that it can set the correct TTL on the Redis entry. There are two options:
-
The token response includes the RT expiration — some providers return a field in the token response with the RT expiration (this is not mandated by the OAuth 2.0 spec). Set
refreshTokenExpirationPayloadFieldto the name of that field and the BFF will read the value automatically:{
"session": {
"refreshTokenExpirationPayloadField": "refresh_token_expires_in"
}
} -
Set it manually — when the AS does not include the RT expiration in the response, you must configure it by hand. Set
refreshTokenExpirationSecsto match the RT lifetime configured on the AS (default: 7 days /604800):{
"session": {
"refreshTokenExpirationSecs": 604800
}
}
If both fields are set, the value from the token response (refreshTokenExpirationPayloadField) takes precedence.
3. Cookie (Session) Expiration
The cookie expirationSecs field inside mode controls how long the browser keeps the session cookie.
It is good practice to align this value with the SSO session max configured on the AS so that the cookie and the server-side session expire at the same time.
{
"session": {
"mode": {
"type": "bff",
"expirationSecs": 86400
}
}
}
Refresh Token Rotation
When refreshTokenRotation is set to true, each token refresh issues a new RT and invalidates the previous one.
This must match the AS client configuration — enable it only if the AS is configured for RT rotation.
The authtool_bff service protects refresh-token operations with an internal lock to prevent race conditions (e.g. two concurrent requests attempting to refresh the same token simultaneously).
This lock is process-local, which is sufficient for multi-replica deployments as long as rotation is disabled (refreshTokenRotation: false), because re-using the same RT is idempotent.
When refresh token rotation is enabled, the service must run as a single replica.
With rotation, each RT can only be used once — if two replicas attempt to refresh concurrently, one will receive a 400 error from the Authorization Server (the RT has already been consumed and invalidated), causing the BFF to destroy the session and force the user to re-authenticate.
Full Example
{
"session": {
"authorizationFlowCache": { "connectionName": "redis-session" },
"refreshTokenExpirationSecs": 604800,
"refreshTokenRotation": false,
"sessionJitterSecs": 7
}
}
authorizationFlowCache— points to a Redis connection either by reference (connectionName) or inline Redis config, where tokens and authorization state are persistedrefreshTokenExpirationSecs— TTL for stored refresh tokens (default: 7 days /604800); should match the AS RT lifetimerefreshTokenRotation— whentrue, each refresh issues a new RT and invalidates the old one (default:false); must match the AS client settingrefreshTokenExpirationPayloadField— optional key in the token response containing the RT expiration (not mandated by OAuth 2.0 spec)sessionJitterSecs— subtracts seconds from token expiry to avoid using tokens that expire mid-flight (default:7)
Redis client prefixes keys with a namespace from connections.<name>.namespace.
When multiple sessions are needed for different OAuth 2.x clients (different Client IDs), the
same Redis instance can be used with the same namespace (defaults to authtool_bff).
Otherwise, the namespace can be used to separate keys on the same Redis instance.
Subsequent Requests
On every request carrying the session cookie:
- BFF verifies the cookie signature using
signingKey - Looks up the session in Redis
- Decrypts the stored access token using
encryptionKey - If the AT is expired (minus jitter), uses the RT to refresh
- Returns the valid AT (in BFF mode, only internally; in token mediation mode, to the caller)
User Claims
The userClaims array selects which ID token claims are stored in the session and exposed via /session:
{
"session": {
"userClaims": ["email", "name", "picture"]
}
}
Defaults to ["email"].
Error Handling
The errorRedirects field accepts a URL where users are redirected when session or token errors occur:
{
"session": {
"errorRedirects": "https://mydomain.com/error"
}
}
OpenID Connect Integration
The authtool_bff acts as an OAuth 2.0 confidential client that manages the Authorization Code flow with an external Identity Provider (IdP) or Authorization Server (AS). The BFF securely stores tokens and user claims in the session, never exposing sensitive material to the browser.
Configuration options:
-
Issuer and Metadata Discovery:
Theissuerfield specifies the base URL of the IdP/AS. TheissuerMetadataSegment(default:/.well-known/openid-configuration) is appended to discover OIDC endpoints automatically. -
Client Registration:
TheclientId,redirect_uri, andcredentialsfields register the BFF as an OIDC client. Credentials can be a client secret or a private key. -
Scopes and Extra Parameters:
Thescopefield defines requested scopes (e.g.,openid,profile,email). Theextrafield allows custom query parameters (e.g.,audiencefor Auth0). -
Token Endpoint Authentication:
ThepreferredTokenEndpointAuthMethodandpreferredTokenEndpointSigningAlgorithmfields let you override the default authentication and signing algorithms, supporting providers with non-standard requirements. -
ID Token Validation:
TheidTokenValidationIssuerfield allows custom issuer validation, useful when the IdP issues tokens with a different issuer than advertised in metadata. -
Logout Flow:
ThepostLogoutRedirectUrifield enables OIDC-compliant logout, redirecting users to a registered URI after logging out at the IdP.
How OIDC integrates with session management:
- After successful authentication, the BFF stores access and refresh tokens (encrypted) in Redis and sets a secure, signed session cookie.
- User claims from the ID token (as configured in
userClaims) are extracted and exposed via the/sessionendpoint. - The session lifecycle (creation, refresh, expiration, logout) is managed transparently using OIDC endpoints.
- All sensitive operations (token exchange, refresh, logout) happen server-side.
Redis Integration
The BFF uses Redis as a centralized cache for session data, access tokens, and refresh tokens. The authorizationFlowCache field in the session configuration references a Redis connection, either by name or inline.
- Token Storage:
Access and refresh tokens are encrypted and stored in Redis, keyed by the session identifier. - Session Lookup:
On each request, the BFF retrieves and decrypts session data from Redis to validate and refresh tokens as needed. - Configuration:
Redis connections are defined in theconnectionsblock and referenced viaauthorizationFlowCache.
This design supports horizontal scaling, as all BFF instances share the same session store.
Server Settings
The server section configures the BFF REST API server:
- Listening Address and Port:
Controls which network interface and port the BFF binds to, suitable for deployment behind a reverse proxy or API gateway. - API Prefix:
TheapiPrefixsetting groups all BFF endpoints under a common URL path (e.g.,/bff/), simplifying routing and API gateway integration.
BFF Key Setup Script
The setup_bff_keys.sh script automates the generation of cryptographic keys required by the BFF for cookie signing and token encryption.
The script generates values for the SESSION_SIGNING_KEY and SESSION_ENCRYPTION_KEY environment variables, which the BFF configuration references to sign session cookies and encrypt tokens at rest in Redis.
You can download the latest version of the script using:
version=latest
curl -o setup_bff_keys.sh "https://cdn.mia-platform.eu/runtime/platform/auth/authtool/authtool_bff/${version}/setup_bff_keys.sh"
# enable execution
chmod +x setup_bff_keys.sh
Private Key / Public Key for private_key_jwt Assertion
When configuring the BFF to use private_key_jwt authentication, the client proves its identity by signing a JWT with its private key instead of sending a client secret. The Authorization Server verifies the JWT using the registered public key.
How it works:
- The private key is referenced in the configuration under
credentials(type:privateKey). - During the token exchange, the BFF creates and signs a JWT assertion, presenting it to the token endpoint as proof of identity.
- The Authorization Server must have the matching public key registered for the client.
Key points:
- This method is more secure than a client secret because the private key never leaves the BFF.
- The private key should be generated and stored securely, using the
setup_bff_keys.shscript or a similar process. - The public key must be registered with the IdP during client registration.
Example configuration snippet:
{
"client": {
"credentials": {
"privateKey": {
"type": "file",
"path": "/path/to/key.pem"
}
},
"preferredTokenEndpointAuthMethod": "private_key_jwt"
}
}
This setup provides strong asymmetric authentication between the BFF and the Authorization Server.
Endpoint Rewrites
Due to how the Mia Platform Console manages Envoy routing, every BFF route must be explicitly declared in the endpoints.yaml advanced configuration file. Each route needs both a path match (exact) and a prefix match (with trailing slash) to handle requests correctly.
All BFF routes must disable the Lua token-exchange filter, since these endpoints handle authentication themselves and must not go through the token-exchange pipeline. Depending on your setup, you may also need to disable jwt_authn (when JWT validation covers the whole listener) and/or ext_authz (when external authorization is active).
Add the following entries to endpoints.yaml:
## BFF ##
### login
- listener_name: frontend
match:
prefix: /bff/login/
route:
prefix_rewrite: /bff/login/
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
- listener_name: frontend
match:
path: /bff/login
route:
prefix_rewrite: /bff/login
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
### logout
- listener_name: frontend
match:
prefix: /bff/logout/
route:
prefix_rewrite: /bff/logout/
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
- listener_name: frontend
match:
path: /bff/logout
route:
prefix_rewrite: /bff/logout
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
### oidc/logout/callback
- listener_name: frontend
match:
prefix: /bff/oidc/logout/callback/
route:
prefix_rewrite: /bff/oidc/logout/callback/
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
- listener_name: frontend
match:
path: /bff/oidc/logout/callback
route:
prefix_rewrite: /bff/oidc/logout/callback
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
### session
- listener_name: frontend
match:
prefix: /bff/session/
route:
prefix_rewrite: /bff/session/
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
- listener_name: frontend
match:
path: /bff/session
route:
prefix_rewrite: /bff/session
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
### oauth/callback
- listener_name: frontend
match:
prefix: /bff/oauth/callback/
route:
prefix_rewrite: /bff/oauth/callback/
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
- listener_name: frontend
match:
path: /bff/oauth/callback
route:
prefix_rewrite: /bff/oauth/callback
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
Each route requires two entries — one with path (exact match) and one with prefix (trailing slash) — to ensure that both /bff/login and /bff/login/?redirect_uri=... are routed correctly.
Alternative: Cluster-Based Routing
Instead of declaring every BFF route individually in endpoints.yaml, you can skip the Console Endpoints section entirely and route all /bff traffic through a single catch-all entry. This approach requires three steps:
1. Do not create a BFF endpoint in the Console Design section
The authtool-bff microservice should still be deployed in your project, but you should not create an endpoint for it in the Console's Design > Endpoints section. Routing will be handled entirely via Envoy advanced configuration.
2. Add the authtool-bff cluster in clusters.yaml
Since the Console is not managing the endpoint, you need to register the BFF as an Envoy cluster manually:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: "authtool-bff"
connect_timeout: 30s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: "authtool-bff"
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: "authtool-bff"
port_value: 3000
Adjust address and port_value to match your service name and listening port.
3. Add a single /bff prefix route in endpoints.yaml
Instead of one entry per BFF route, add a single prefix match that catches all /bff traffic:
## BFF (catch-all)
- listener_name: frontend
match:
prefix: /bff
route:
prefix_rewrite: /bff
cluster: "authtool-bff"
timeout: "0s"
typed_per_filter_config:
envoy.filters.http.lua:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
disabled: true
# activate when jwt_authn covers the whole listener
# envoy.filters.http.jwt_authn:
# "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig
# disabled: true
# activate when ext_authz is active
# envoy.filters.http.ext_authz:
# "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute"
# disabled: true
This single entry replaces all the per-route declarations from the previous section. The trade-off is that the BFF service is not visible in the Console's Endpoints UI, and any future BFF routes are automatically covered without configuration changes.