Appearance
Configuration
The bridge reads all of its settings from environment variables only. The web host and every saml-bridge command use the same source. appsettings*.json files and command-line arguments are never read. Two of the settings are secrets and must be set in the environment, never on the command line: the master key and the client secret. Nothing is read from request headers. The web host refuses to start if a required value is missing or invalid.
| Variable | Required | Purpose |
|---|---|---|
SAMLBRIDGE_MASTER_KEY | yes | Base64 of 32 random bytes. Encrypts the signing key. Generate one with openssl rand -base64 32. Keep it out of the database and out of source control. |
SAMLBRIDGE_PUBLIC_URL | yes | The bridge's public origin, for example https://sso.example.test. Must be https, with no path, query, or fragment. It sets the issuer, the entity ID, and every endpoint URL. |
SAMLBRIDGE_OIDC_ISSUER | yes | The issuer URL of the OIDC provider, an https URL with no query or fragment. |
SAMLBRIDGE_OIDC_CLIENT_ID | yes | The client ID of the bridge's confidential client at the OIDC provider. |
SAMLBRIDGE_OIDC_CLIENT_SECRET | yes | The client secret of that client. It is not stored in the database (see "Spec deviations" in PROJECT.md) and is never logged. |
SAMLBRIDGE_DATABASE | no | Path to the SQLite file. apps and the web host use the same file. Defaults to saml-bridge.db in the current working directory. |
ASPNETCORE_URLS | no | The address the web host listens on, for example http://+:8080. |
The master key and the client secret are refused when they are passed as a command-line argument, so saml-bridge exits with an error and reads nothing. Prefer a secret store or an --env-file kept out of version control to pass them to the process.
Startup
On start, the web host:
- applies the SQLite migrations,
- loads the bridge signing key, or creates one on first start (RSA-2048, self-signed, valid for five years), and stores its private key encrypted with the master key. Exactly one key must be active. Otherwise the bridge refuses to start. The key lifecycle is in Rotate the signing key.
The OIDC redirect URI is {SAMLBRIDGE_PUBLIC_URL}/saml/callback. Register exactly that URI for the client at the OIDC provider.
OIDC client
Register the bridge as a confidential client at the OIDC provider:
- Redirect URI:
{SAMLBRIDGE_PUBLIC_URL}/saml/callback, exact match. - Grant: authorization code, with PKCE (
S256). - Scopes: the bridge requests
openidplus the scopes set for each app withapps set --scope(see SSO). The provider must allow each scope an app uses for this client, or the claims won't be issued.
At startup, and again at login, the bridge checks the provider's discovery document. It refuses to start or log in when any of these rules fails:
- The discovery issuer equals
SAMLBRIDGE_OIDC_ISSUER. codeis inresponse_types_supported.S256is incode_challenge_methods_supported.- The ID token signing algorithms are
RS256and/orES256, intersected withid_token_signing_alg_values_supported. The intersection must not be empty.noneandHS*are never used. - The authorization response carries
iss, and it matches the issuer. - The ID token carries
auth_time. - Token endpoint authentication is
client_secret_basicby default.client_secret_postis used only when the provider advertises it. A provider that advertises neither method is refused. An empty list counts as absent (see "Spec deviations" inPROJECT.md).
Details and the spec references are in §2.1 of PROJECT.md.
Example: ValkyrieID
ValkyrieID is one OIDC provider that works with the bridge. Its settings are the three OIDC variables. Replace the placeholders with your values:
sh
SAMLBRIDGE_OIDC_ISSUER=https://valkyrie.example.test
SAMLBRIDGE_OIDC_CLIENT_ID=<client-id>
SAMLBRIDGE_OIDC_CLIENT_SECRET=<client-secret>The example app needs these scopes allowed for the bridge's client: openid, profile, and email, which the default app mapping uses. Allow roles or groups only when an app maps those claims.
The master key is not stored in the database. Losing it makes the encrypted signing keys unrecoverable, including any inactive key not yet retired. The client secret is not encrypted by the master key; it is read from the environment (see "Spec deviations" in PROJECT.md).