Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Demo: two-IdP federation

Location: contrib/demo/federation/

Starts two ahdapa instances on loopback ports 8080 and 8081 and demonstrates section-6 authentication delegation: a downstream IdP (IdP A) redirects users to an upstream IdP (IdP B) for authentication, then maps the returned identity to a local account.

Topology

InstanceRoleURLRealm
IdP A — “Downstream Corp”downstream (relying) IdPhttp://127.0.0.1:8080CORP.LOCAL
IdP B — “Partner IdP”upstream (authenticating) IdPhttp://127.0.0.1:8081PARTNER.LOCAL

What it shows

  • Federation loginalice@CORP.LOCAL is linked to bob@PARTNER.LOCAL. Entering alice at IdP A’s login page redirects the browser to IdP B; the user authenticates as bob there, and IdP A issues a local session for alice.
  • Local logincarol@CORP.LOCAL logs in with a local password at IdP A without any federation redirect.
  • Two-stage login UX — the login page first asks for a username, calls GET /api/auth/federated-hint to check for a federation hint, then either redirects to the upstream (federated case) or shows a password field (local case).
  • OIDC dynamic client registration — IdP A registers itself as a client at IdP B via POST /register (RFC 7591) during setup; the assigned client_id is substituted into IdP A’s runtime config.
  • Federated account linking — the admin API (POST /api/admin/federated-accounts) creates the bob@PARTNER.LOCAL → alice@CORP.LOCAL mapping; no manual database editing is needed.
  • Upstream token store — upstream tokens from IdP B are automatically encrypted and stored during federated login; retrievable via SPNEGO (curl --negotiate) when Kerberos tools are installed.
  • Device authorization grant with return_to — a device-flow client requests a device code, then the user authenticates via the federated login flow with return_to=/device?user_code=... so that after upstream authentication the browser lands on the device consent page instead of the default WebUI.
  • Upstream token refresh verification — after a device-flow federation login deposits new upstream tokens, the demo verifies that the previously stored token has been replaced by the fresh one.

Demo accounts

Passwords are generated randomly at startup and printed to the console.

UsernameIdPWhat happens on login at IdP A
aliceA (local)Redirect to IdP B; log in as bob; redirected back as alice
carolA (local)Local password login; no federation redirect
bobB (local)Direct login at IdP B only
dianaB (local)Direct login at IdP B only

Prerequisites

  • ahdapa binary — the script looks in $PATH first (resolved to its full path), then target/release/ahdapa, then target/debug/ahdapa, and falls back to cargo build if none is found.
  • python3 — for JSON parsing in the setup script.
  • curl.
  • openssl — for generating the EC P-256 key used in the upstream client auth stanza.
  • npm — if the WebUI dist/ directory does not yet exist, the script builds it.
  • Ports 8080 and 8081 free.

Running

# Non-interactive (automated test, exits with pass/fail):
contrib/demo/federation/run.sh

# Interactive (run tests, then keep servers up for manual exploration):
contrib/demo/federation/run.sh --interactive

What the script does

  1. Checks that ports 8080 and 8081 are free; exits with an error if not.
  2. Builds the WebUI (webui/dist/) if the directory is absent, then locates or builds the ahdapa binary (PATH → release → debug → cargo build).
  3. Generates a P-256 private key for IdP A’s upstream client auth stanza (stored at /tmp/ahdapa-demo-idpa-upstream.pem).
  4. If Kerberos tools are installed, starts an ephemeral MIT KDC for realm CORP.LOCAL with alice@CORP.LOCAL and HTTP/localhost@CORP.LOCAL principals.
  5. Starts IdP B on port 8081; waits for its discovery document to be served.
  6. Registers IdP A as a public OIDC client at IdP B via POST http://127.0.0.1:8081/register using the demo registration token (demo-federation-setup-token — set in idpb.toml). The response contains the generated client_id.
  7. Writes a runtime config for IdP A at /tmp/ahdapa-demo-idpa-runtime.toml by substituting the real client_id and keytab path into the template.
  8. Starts IdP A on port 8080; waits for its discovery document.
  9. Logs in as alice at IdP A via the admin API, then calls POST /api/admin/federated-accounts to link bob@PARTNER.LOCAL to alice@CORP.LOCAL.
  10. Runs automated tests:
    • Drives a scripted federated login (alice → IdP B → bob → callback).
    • Verifies upstream-token.deposited audit event in idpa.log.
    • If KDC is available: kinit + SPNEGO retrieval of the stored upstream token.
  11. Prints PASS/FAIL summary.
  12. In interactive mode: prints URLs and waits for Ctrl-C.

Example non-interactive output

==> Starting IdP A (Downstream Corp) on :8080…
    Waiting for IdP A. ready.
==> Creating admin session at IdP A (alice)…
==> Linking bob@PARTNER.LOCAL → alice@CORP.LOCAL…
    bob@PARTNER.LOCAL  →  alice@CORP.LOCAL

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Step 1 — drive federated login (alice → bob) to store upstream token
  ✓ Federation redirect to IdP B
  ✓ Got callback URL for IdP A
  ✓ Federation callback completed (HTTP 302)

  Step 2 — verify upstream token was deposited
  ✓ upstream-token.deposited audit event found in idpa.log

  Step 3 — retrieve upstream token via SPNEGO
  ✓ Retrieved upstream token via SPNEGO (HTTP 200)
  ✓ upstream-token.retrieved audit event found in idpa.log

  Step 5 — device authorization grant with federated login + return_to
  ✓ Got device code (user_code: ABCD-EFGH)
  ✓ Federation redirect to IdP B (with return_to)
  ✓ Callback redirected to device verification page (return_to works)
  ✓ Device authorized
  ✓ Device token obtained

  Step 6 — re-retrieve upstream token after device-flow federation
  ✓ Upstream token refreshed after device-flow federation

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  PASS — all federation demo steps succeeded.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Exploring the demo

With --interactive, the servers stay running after the automated tests. Open http://127.0.0.1:8080/ui/ in a browser.

Federated login path:

  1. Enter alice in the username field and press Enter.
  2. The page detects the federation hint and redirects to IdP B’s login form.
  3. Enter bob and the password printed at startup at IdP B.
  4. IdP B redirects back to IdP A’s callback (/internal/callback/partner-idp).
  5. IdP A maps bob@PARTNER.LOCAL to alice@CORP.LOCAL and issues a local session.
  6. The admin panel shows alice as the logged-in user.

Local login path:

  1. Enter carol and press Enter.
  2. A password field appears (no federation hint for carol).
  3. Enter the password printed at startup. Session is established at IdP A directly.

To initiate the federation flow from a client application, redirect to:

http://127.0.0.1:8080/auth/external/partner-idp

Append standard OAuth2 parameters (client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method) as query parameters.

Upstream token store

The demo enables the upstream token store on IdP A (store_on_federation = true). When alice performs a federated login (redirected to IdP B, logs in as bob), the upstream tokens from IdP B are automatically encrypted and stored in the CRDT.

If Kerberos tools are installed (krb5-server, krb5-workstation), the script starts an ephemeral MIT KDC for realm CORP.LOCAL. After a federated login, retrieve the stored upstream token with SPNEGO:

# Get a Kerberos ticket for alice (password printed at startup)
KRB5_CONFIG=/tmp/ahdapa-demo-federation-kdc/krb5.conf \
  kinit alice@CORP.LOCAL

# Retrieve the stored upstream token via SPNEGO.
# Use "localhost" (not 127.0.0.1) so SPNEGO targets HTTP/localhost@CORP.LOCAL.
curl --negotiate -u: -X POST \
  http://localhost:8080/api/upstream-token/retrieve

The response contains the upstream IdP B access token:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 842,
  "scope": "openid profile email"
}

Without Kerberos tools, the token store is still enabled and tokens are stored on federated login, but SPNEGO retrieval is not available. The demo prints a note at startup if Kerberos tools are missing.

Configuration notes

FileDescription
idpa.tomlIdP A base config; __IDPA_CLIENT_ID__ placeholder substituted at runtime
idpb.tomlIdP B config; registration_token = "demo-federation-setup-token" enables POST /register
users-idpa.toml.inUsers template for IdP A (alice, carol); passwords substituted at runtime
users-idpb.toml.inUsers template for IdP B (bob, diana); passwords substituted at runtime
clients-idpa.tomlStatic OAuth2 clients for IdP A: token-retriever (upstream token retrieval) and demo-device (device authorization grant)

The upstream IdP stanza in IdP A’s config:

[[federation.upstream_idps]]
id               = "partner-idp"
issuer           = "http://127.0.0.1:8081"
client_id        = "<substituted at runtime>"
private_key_path = "/tmp/ahdapa-demo-idpa-upstream.pem"
scopes           = ["openid", "profile", "email"]
callback_path    = "/internal/callback/partner-idp"

IdP A trusts tokens issued by IdP B:

[federation]
trusted_issuers = ["http://127.0.0.1:8081"]

See also