Olympus Docs
CookbookEnterprise SSO

SAML to OIDC bridge

Federate SAML-only apps with Olympus

Some legacy enterprise apps only speak SAML 2.0. Olympus is OIDC-first. Bridge with a SAML-to-OIDC translator.

Architecture

SAML SP (your legacy app) → SAML IdP (the bridge) → OIDC RP (the bridge again) → Olympus Hydra

The bridge acts as:

  • A SAML Identity Provider to your legacy app.
  • An OIDC Relying Party to Olympus Hydra.

Bridge options

  • SAMLify, Node.js library to build your own.
  • DEX, Go, supports multiple connectors including SAML on one side, OIDC on the other (with custom config).
  • Authentik, full IdP that can speak SAML and OIDC; deploy alongside.
  • Keycloak, overkill but works.

DEX as the bridge

Probably the cleanest option:

# dex-config.yaml
issuer: https://saml-bridge.example.com
storage:
  type: postgres
  config:
    host: postgres
    database: dex

# DEX speaks OIDC to clients (this is where SAML apps would NOT work, DEX is OIDC-only).
# For SAML, you'd need to combine DEX with SAML signing on the client side.

Actually, DEX is OIDC-only as a public interface. For SAML, look at:

  • Authentik, flexible, supports SAML SP + IdP.
  • Keycloak, supports SAML and OIDC bidirectionally.
  • Custom code, Node.js SAML library + custom OIDC client to Olympus.

Custom bridge

// pseudo-code
import * as saml from "samlify";
import { Issuer } from "openid-client";

const olympus = await Issuer.discover(process.env.OLYMPUS_ISSUER);
const olympusClient = new olympus.Client({
  client_id: process.env.BRIDGE_CLIENT_ID,
  client_secret: process.env.BRIDGE_CLIENT_SECRET,
});

const samlIdP = saml.IdentityProvider({
  // bridge identity provider metadata
});

app.get("/saml/sso", async (req, res) => {
  // 1. SAML AuthnRequest comes in from the SP
  const samlReq = await samlIdP.parseLoginRequest(saml.spExternal, "redirect", req);

  // 2. Redirect to Olympus OIDC
  const url = olympusClient.authorizationUrl({
    scope: "openid email profile",
    state: samlReq.relayState,
    code_challenge: ...,
  });
  res.redirect(url);
});

app.get("/oidc/callback", async (req, res) => {
  // 3. Olympus returns
  const tokens = await olympusClient.callback("/oidc/callback", req.query);
  const userinfo = await olympusClient.userinfo(tokens.access_token);

  // 4. Issue SAML response back to the SAML SP
  const samlResp = await samlIdP.createLoginResponse(saml.spExternal, ..., "post", {
    NameID: userinfo.sub,
    email: userinfo.email,
  });
  res.send(samlResp.context); // auto-POST form
});

Fragile if you write from scratch. Prefer Authentik or Keycloak.

When to bridge vs. drop the SAML SP

Modern enterprise apps support OIDC. Old ones don't:

  • Atlassian Cloud, OIDC supported.
  • Salesforce, OIDC supported.
  • Workday, SAP, typically SAML.
  • AWS IAM Identity Center, SAML or OIDC.

If you can update the SP to use OIDC, do that instead of running a bridge.

Operational cost

A bridge is another service to operate:

  • More cert rotation.
  • More IdP metadata to manage.
  • Failure point in the auth path.

Justify only if you have multiple SAML-only SPs.

On this page