Skip to content

feat: Add use_oidc_discovery_issuer option for generic OIDC provider (Azure AD B2C support) #4536

@getlarge

Description

@getlarge

Preflight checklist

  • I could not find a solution in the existing issues, FAQ, or Ory documentation
  • I agree to follow the Ory Community Code of Conduct
  • I have read and am following this repository's contribution guidelines
  • This feature request has not been reported before

Problem

Azure AD B2C violates OIDC Discovery spec §4.3: the issuer in the discovery document does not match the URL used to fetch it.

Discovery URL:  https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy>/v2.0/.well-known/openid-configuration
Issuer returned: https://<tenant>.b2clogin.com/<tenant-id>/v2.0/

The discovery URL must include the B2C policy name, but the returned issuer uses the tenant GUID and omits the policy. This mismatch is unavoidable — all B2C discovery URL variants (tenant-name, tenant-id, /tfp/ prefix) return the same GUID-based issuer.

Kratos (via go-oidc) strictly validates issuer == discovery URL and rejects the provider:

oidc: issuer did not match the issuer returned by provider,
expected "https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy>/v2.0"
got "https://<tenant>.b2clogin.com/<tenant-id>/v2.0/"

Why existing provider types don't work

Provider Behavior Result
microsoft Hardcodes login.microsoftonline.com/{tenant} Redirects to generic MS login, not B2C custom flow
generic + issuer_url OIDC Discovery with strict issuer validation Issuer mismatch error

B2C-side mitigation

Azure Portal has a "Token compatibility > Issuer (iss) claim" setting, but the tfp issuer format may only be available with B2C custom policies, not standard user flows. This places the burden on the customer to migrate from user flows to custom policies — a non-trivial effort.

Proposed solution

Add a use_oidc_discovery_issuer boolean field to the generic OIDC provider configuration. When true, use go-oidc's InsecureIssuerURLContext to allow the discovery URL to differ from the issuer.

Ory's own go-oidc fork (github.com/ory/go-oidc/v3) already has InsecureIssuerURLContext — this change just wires it into the generic provider.

Key points:

  • Opt-in — no behavior change for existing configs (defaults to false)
  • ID token issuer validation still occurs — tokens are verified against the issuer from the discovery document. Only the spec requirement that discovery URL == issuer is relaxed.
  • ~5 line code change + schema update + tests
  • @aeneasr (kind of)endorsed InsecureIssuerURLContext as the correct approach in April 2022

Configuration example

{
  "id": "azure-b2c",
  "provider": "generic",
  "client_id": "...",
  "client_secret": "...",
  "issuer_url": "https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/<policy>/v2.0",
  "use_oidc_discovery_issuer": true,
  "scope": ["openid"],
  "claims_source": "id_token",
  "mapper_url": "base64://..."
}

Related issues

Implementation

PR ready: #4537

Changes

  1. selfservice/strategy/oidc/provider_config.go — add UseOIDCDiscoveryIssuer field to Configuration
  2. selfservice/strategy/oidc/provider_generic_oidc.go — use InsecureIssuerURLContext in provider() when flag is set
  3. embedx/config.schema.json — add use_oidc_discovery_issuer property
  4. selfservice/strategy/oidc/provider_generic_test.go — 3 test cases covering: mismatch rejection (default), mismatch acceptance (flag=true), endpoint discovery verification

Tested end-to-end against a real Azure AD B2C tenant with standard user flows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions