Preflight checklist
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
selfservice/strategy/oidc/provider_config.go — add UseOIDCDiscoveryIssuer field to Configuration
selfservice/strategy/oidc/provider_generic_oidc.go — use InsecureIssuerURLContext in provider() when flag is set
embedx/config.schema.json — add use_oidc_discovery_issuer property
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.
Preflight checklist
Problem
Azure AD B2C violates OIDC Discovery spec §4.3: the
issuerin the discovery document does not match the URL used to fetch it.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:
Why existing provider types don't work
microsoftlogin.microsoftonline.com/{tenant}generic+issuer_urlB2C-side mitigation
Azure Portal has a "Token compatibility > Issuer (iss) claim" setting, but the
tfpissuer 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_issuerboolean field to the generic OIDC provider configuration. Whentrue, usego-oidc'sInsecureIssuerURLContextto allow the discovery URL to differ from the issuer.Ory's own go-oidc fork (
github.com/ory/go-oidc/v3) already hasInsecureIssuerURLContext— this change just wires it into the generic provider.Key points:
false)InsecureIssuerURLContextas the correct approach in April 2022Configuration 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
issvalue #2404 — Original request for InsecureIssuerURLContext support (2022)Implementation
PR ready: #4537
Changes
selfservice/strategy/oidc/provider_config.go— addUseOIDCDiscoveryIssuerfield toConfigurationselfservice/strategy/oidc/provider_generic_oidc.go— useInsecureIssuerURLContextinprovider()when flag is setembedx/config.schema.json— adduse_oidc_discovery_issuerpropertyselfservice/strategy/oidc/provider_generic_test.go— 3 test cases covering: mismatch rejection (default), mismatch acceptance (flag=true), endpoint discovery verificationTested end-to-end against a real Azure AD B2C tenant with standard user flows.