app-a.com -> login.app-a.com -> IdP (session already exists)
-> callback -> Gate One session for app-a.com
app-b.fr -> login.app-b.fr -> same IdP (same session)
-> callback -> Gate One session for app-b.frSSO across different domains
Useful if you want to…
give users one login across
app-a.comandapp-b.fr;keep one cookie per domain instead of trying to share a cookie across unrelated registrable domains;
declare the OIDC/client/session settings once and avoid divergent copies;
declare the application route body once when the backend is the same.
Gate One cannot set one cookie for app-a.com and app-b.fr: browsers
reject a Domain that is not a suffix of the current host. Cross-TLD SSO
therefore relies on the IdP session, exactly like the multi-subdomain
case relies on cookieDomain.
What the user sees
There is one redirect per domain, but no login form while the IdP session is valid. If the IdP session is expired, the normal interactive login is required. This is not a handoff protocol and it does not create a cross-TLD Gate One cookie.
Configuration
The shared settings live once on the ssoGroups entry. Only the
domain-specific fields are repeated in providers[]:
{
"ssoGroups": [{
"id": "company",
"type": "oidc",
"issuer": "https://idp.example.com/realms/company",
"clientId": "gate-one",
"clientSecretEnv": "OIDC_CLIENT_SECRET",
"scopes": ["openid", "profile", "email"],
"roleClaims": ["roles"],
"sessionKeyEnv": "OIDC_SESSION_KEY",
"sessionStore": {
"type": "redis",
"url": "redis://redis:6379/0",
"passwordEnv": "REDIS_PASSWORD",
"prefix": "cladone:auth:v1:"
},
"backchannelLogout": true,
"providers": [
{
"id": "app-a",
"loginBaseUrl": "https://login.app-a.com",
"cookieDomain": "app-a.com",
"defaultAppUrl": "https://app-a.com/"
},
{
"id": "app-b",
"loginBaseUrl": "https://login.app-b.fr",
"cookieDomain": "app-b.fr",
"defaultAppUrl": "https://app-b.fr/"
}
]
}],
"defaultAuthProvider": "app-a",
"routes": [{
"hosts": ["app-a.com", "app-b.fr"],
"path": "/*",
"frontend": true,
"backend": "http://app:8080",
"auth": {}
}]
}providers[] only accepts id, loginBaseUrl, loginUrl,
cookieDomain, cookieName, redirectUrl, defaultAppUrl, links
and mcpAudience.
A shared field such as issuer, clientId or sessionStore placed in a
provider is rejected by the strict decoder.
routes[].hosts[] expands one route body into one concrete route per
host; auth.provider is inferred from the provider whose
cookieDomain covers the host. hosts is mutually exclusive with
host and wildcard.
Why Redis is required for a multi-domain group
Each domain has its own Gate One session cookie. A group with more than
one provider must use sessionStore.type: "redis" with the same
sessionKeyEnv on every provider: the revocation state (and the
sub/sid global logout) is then shared, so a logout on app-a.com is
refused on app-b.fr on the next request.
Without shared state, app-b.fr can keep accepting its local cookie
until it expires even after a logout on app-a.com.
A provider’s defaultAppUrl is used when someone visits that provider’s
login host root; it overrides the global defaultAppUrl and must point
to a route of that provider (or a host under its cookieDomain).
IdP setup
Register every callback:
If you use back-channel logout:
if the IdP allows one URL per client, point it at one login host and keep the same issuer and Redis revocation prefix on every provider. The receiving Gate One instance revokes the session in the shared store; the other domain checks that store on the next request;
if the IdP supports one URL per client only and the stores are not shared, this single URL cannot cover both domains: use a second client or a group-level subject revocation instead.
Both domains must use the same issuer and the same user identity
(issuer + sub). If you use different IdPs per domain, the IAM’s
account linking is not enough yet: Gate One’s subjectId is per
(issuer, sub).
Important |
|
Complete example
routes.json and
README.md
Configuration reference
ssoGroups[.providers]routes[.hosts]ssoGroups[.sessionStore]