SSO across different domains

One IdP login across unrelated domains, with one provider per domain and a shared IdP session.

Useful if you want to…

  • give users one login across app-a.com and app-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

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.fr

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
  • Zero form, not zero redirect: the IdP round-trip remains.

  • The IdP must reuse its own session; Gate One sends the provider’s default prompt.

  • A cookie is never shared across TLDs; logout propagation relies on back-channel logout or the shared Redis revocation state.

  • With IAM local accounts, the IAM currently renders its local login page on /authorize even when it has a session. Use an upstream IdP for the zero-form path, or add an IAM session short-circuit.

  • Applications on the same TLD should use cookieDomain instead; it avoids the extra redirect and is simpler.

Complete example

Configuration reference