npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@o3co/auth-provider-federation-oidc

v0.15.0

Published

Generic OpenID Connect federation provider for auth.provider — any OIDC-compliant IdP, configured by issuer

Downloads

327

Readme

@o3co/auth-provider-federation-oidc

Generic OpenID Connect federation provider for auth.provider: any OIDC-compliant identity provider — Okta, Entra ID, Auth0, Keycloak, a customer's own tenant — from configuration alone, and as many instances as a deployment has issuers (#524).

The FederationProvider contract lives in @o3co/auth-provider-session; the Google, GitHub and Apple packages implement it for one IdP each. This package implements it for every IdP that publishes an OpenID Connect discovery document, so adding an IdP is a config section, not a package.

Usage

Each instance is one module, made by oidcFederationModule(<name>). Every instance reads its config from the shared oidcFederationConfigs slot, which the composition root fills — normally straight from the federations config section with readOidcFederationConfigs:

import { createApp, defineModule } from "@o3co/auth-provider-core";
import {
  oidcFederationModule,
  oidcFederationNames,
  readOidcFederationConfigs,
} from "@o3co/auth-provider-federation-oidc";
import { sessionModule } from "@o3co/auth-provider-session";

const oidcConfigBridgeModule = defineModule({
  name: "oidc-federation-config",
  requires: ["config"] as const,
  provides: {
    oidcFederationConfigs: ({ config }) => readOidcFederationConfigs(config.federations),
  },
});

const handle = await createApp({
  modules: [
    sessionModule,
    oidcConfigBridgeModule,
    ...oidcFederationNames(config.federations).map((name) => oidcFederationModule(name)),
    // ... composition-root modules supplying userRepository + the session stores
  ],
  bootstrapComponents: { config, pathResolver },
});

The scaffold (@o3co/create-auth-provider) does exactly this in src/buildModules.mts, so a scaffolded deployment adds an IdP by editing config/application.conf alone.

Configuration

A federations.<name> section whose type is oidc. The section name is the federation name: the browser starts at /session/oauth/federation/<name>, the IdP sends it back to callbackURL, and the identity handed to the Store is <name>:<sub>.

federations {
  okta {
    enabled = true
    type = "oidc"
    issuer = "https://dev-123.okta.com"
    clientId = ${OKTA_CLIENT_ID}
    clientSecret = ${OKTA_CLIENT_SECRET}
    callbackURL = "https://auth.example.com/session/oauth/federation/okta/callback"
    redirectAllowlist = ["https://app.example.com/welcome"]
  }

  keycloak {
    enabled = true
    type = "oidc"
    issuer = "https://sso.example.com/realms/staff"
    clientId = ${KEYCLOAK_CLIENT_ID}
    privateKey = ${KEYCLOAK_PRIVATE_KEY_PEM}     # private_key_jwt
    callbackURL = "https://auth.example.com/session/oauth/federation/keycloak/callback"
    scopes = ["openid", "profile", "email", "groups"]
  }
}

Two issuers, two sections, two callbacks — that is the whole of multi-IdP support. The nested shape (okta { type = "oidc", oidc { ... } }) that extractFederationSection accepts works too.

| Field | Required | Meaning | | --- | --- | --- | | issuer | yes | Issuer identifier, exactly as the IdP writes it into iss. https; plain http only on a loopback host (local Keycloak). | | clientId | yes | Client identifier registered at the IdP. | | clientSecret | one of | client_secret_basic (RFC 6749 §2.3.1). In code the value may be a resolver (() => Promise<string>), consulted on every token request, for secrets that rotate. | | privateKey | one of | private_key_jwt (RFC 7523 / OIDC Core §9). A PEM-encoded PKCS#8 key, or { pem, kid?, alg? }. The JWS algorithm is inferred from the key (RSA → RS256, P-256 → ES256, P-384 → ES384, P-521 → ES512, Ed25519 → EdDSA) unless alg says otherwise; kid goes in the assertion header. | | callbackURL | yes | Where the IdP sends the browser back. The session routes read it from the same section. | | scopes | no | Default ["openid", "profile", "email"]. openid is mandatory — without it there is no id_token — and its absence refuses boot. | | discovery | no | Default true. See below. | | endpoints | no | authorizationEndpoint, tokenEndpoint, jwksUri, userinfoEndpoint, endSessionEndpoint. Applied over the discovered metadata; the first three are mandatory when discovery = false. | | idTokenSignedResponseAlg | no | Pin the id_token JWS algorithm. Otherwise the issuer's advertised id_token_signing_alg_values_supported is trusted; none and symmetric algorithms are never accepted against a JWKS. | | userInfo | no | Default: call UserInfo when the issuer publishes an endpoint. false builds the profile from the id_token alone; true refuses boot if there is no endpoint. | | clockToleranceSeconds | no | Skew tolerated on exp / iat. Default 30. | | redirectAllowlist, sessionDomain, authCallbackUrl, clientUrl | no | The redirect_to policy, as for every federation — see the session package README. |

fetch (code only) replaces the fetch every upstream request goes through — for a proxy, or a test double.

What happens at boot

Discovery. Each instance fetches <issuer>/.well-known/openid-configuration when the app boots, checks the document's issuer against the configured one, and keeps authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint and end_session_endpoint. A failure is fatal: an unreachable issuer, a document naming another issuer, or one without a jwks_uri refuses boot with the federation's name in the error. There is no silent fallback to hand-typed endpoints — a deployment that wants those sets discovery = false and writes them under endpoints, and then no document is fetched at all.

Boot also refuses a config with both clientSecret and privateKey, with neither, with a name that is not one URL path segment, or with a private key that cannot be parsed.

What happens at login

  1. Authorization request — authorization_code with PKCE S256, state and nonce. All three are minted by the session routes per transaction and stored in the session; the provider refuses to build a request without a nonce (OIDC Core §3.1.3.7).
  2. Code exchange — at token_endpoint, authenticated with the configured method, redirect_uri echoing the callback and code_verifier closing the PKCE loop. Before it, the callback's iss parameter (RFC 9207) is compared with the configured issuer, as an exact string: a different one is refused without spending the code. An issuer whose discovered metadata advertises authorization_response_iss_parameter_supported — Keycloak's does by default — must also send one. With discovery = false there is no metadata to advertise it, so iss is compared when present and never required.
  3. ID token validation — signature against the issuer's JWKS (fetched by kid, cached, refetched when an unknown kid appears — but not within a minute of the last fetch, so an IdP that rotates keys must publish the new key before signing with it, which every IdP does); iss equal to the configured issuer; aud containing the client id and no untrusted extra audience; exp and iat within tolerance; nonce equal to the transaction's; at_hash recomputed from the access token when the claim is present (OIDC Core §3.3.2.11). A response without an id_token is refused.
  4. UserInfo — when enabled, fetched with the access token and bound to the id_token's sub; a mismatch is refused. UserInfo values fill email, emailVerified, name, picture and groups, falling back to the id_token's claims. email_verified is normalised to a boolean (some IdPs send "true"); groups is carried only as a string array.
  5. Identity — sub is opaque and stable per issuer; the profile is never keyed on email. The session routes hand <name>:<sub> to the Store exactly as they do for Google or GitHub. An identity the Store does not know is refused with 401 unknown_user — this package provisions nothing; the Store stays the source of truth for who exists.

Any refusal in steps 2–4 surfaces from the callback as 502 exchange_failed (the session routes' answer to an upstream exchange the provider refused) and never reaches the Store.

Optional capabilities

  • SupportsRefresh — refreshToken() runs the refresh_token grant at the issuer.
  • SupportsDelegatedAuthorization (#593) — buildDelegatedAuthorizationUrl() builds the authorization request for a federation grant: the intent's scopes, a required nonce, the RFC 8707 resource, prompt=consent when offline_access is asked for, and the connection's authorizationParams, which may not name a parameter the adapter owns. exchangeDelegatedCode() exchanges the connect callback's code — PKCE, the nonce, the resource at the token endpoint, iss forwarded — never calls UserInfo, and answers the verified id_token's issuer and subject, plus the claims the caller names in identityClaims (#611), copied from that id_token only, as non-empty strings only. refreshDelegatedToken() runs the refresh_token grant with the grant's scopes and resource under the caller's AbortSignal, answers the raw expires_in, scope and token_type, and keeps a rotated refresh token out of an answer the library did not accept — one it could not parse, or one whose id_token it could not verify.
  • SupportsLogout — present only when the issuer publishes an end_session_endpoint (or endpoints.endSessionEndpoint names one): RP-initiated logout with id_token_hint, post_logout_redirect_uri and state.
  • SupportsClaimMapping — mapClaims() promotes email, emailVerified, name, picture and groups; everything else stays namespaced under the federation per the claim precedence rules in the session package.

Scaffold environment variables

The scaffold ships one instance, federations.oidc, disabled by default:

| Variable | Default | Description | | --- | --- | --- | | FEDERATIONS_OIDC_ENABLED | false | Enable the instance | | FEDERATIONS_OIDC_ISSUER | — | Issuer identifier | | FEDERATIONS_OIDC_CLIENT_ID | — | Client ID | | FEDERATIONS_OIDC_CLIENT_SECRET | — | Client secret (client_secret_basic) | | FEDERATIONS_OIDC_CALLBACK_URL | http://localhost:3000/session/oauth/federation/oidc/callback | Callback |

More instances are more sections in config/application.conf.

Public API

  • createOidcProvider(name, config): Promise<OidcProvider> — the provider itself; asynchronous because discovery happens here.
  • oidcFederationModule(name): Module — one const-Module per instance, requiring oidcFederationConfigs and contributing federations.<name> and federationRedirectPolicies.<name>.
  • readOidcFederationConfigs(federations) — the slot from a federations config section; refuses a malformed field by federations.<name>.<field>.
  • oidcFederationNames(federations) — the names of every enabled section of type oidc, sorted.
  • OIDC_FEDERATION_TYPE ("oidc"), DEFAULT_OIDC_SCOPES.
  • Types: OidcProviderConfig, OidcEndpointOverrides, OidcPrivateKey, OidcProvider.