{"record":{"id":"dedeff91a399ca62","repo":"netbirdio/netbird","slug":"identity-provider-issuer-does-not-match-the-issuer","errorCode":null,"errorMessage":"identity provider issuer does not match the issuer returned by the provider","messagePattern":"identity provider issuer does not match the issuer returned by the provider","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/server/types/identity_provider.go","lineNumber":16,"sourceCode":"package types\n\nimport (\n\t\"errors\"\n\t\"net/url\"\n)\n\n// Identity provider validation errors\nvar (\n\tErrIdentityProviderNameRequired      = errors.New(\"identity provider name is required\")\n\tErrIdentityProviderTypeRequired      = errors.New(\"identity provider type is required\")\n\tErrIdentityProviderTypeUnsupported   = errors.New(\"unsupported identity provider type\")\n\tErrIdentityProviderIssuerRequired    = errors.New(\"identity provider issuer is required\")\n\tErrIdentityProviderIssuerInvalid     = errors.New(\"identity provider issuer must be a valid URL\")\n\tErrIdentityProviderIssuerUnreachable = errors.New(\"identity provider issuer is unreachable\")\n\tErrIdentityProviderIssuerMismatch    = errors.New(\"identity provider issuer does not match the issuer returned by the provider\")\n\tErrIdentityProviderClientIDRequired  = errors.New(\"identity provider client ID is required\")\n)\n\n// IdentityProviderType is the type of identity provider\ntype IdentityProviderType string\n\nconst (\n\t// IdentityProviderTypeOIDC is a generic OIDC identity provider\n\tIdentityProviderTypeOIDC IdentityProviderType = \"oidc\"\n\t// IdentityProviderTypeZitadel is the Zitadel identity provider\n\tIdentityProviderTypeZitadel IdentityProviderType = \"zitadel\"\n\t// IdentityProviderTypeEntra is the Microsoft Entra (Azure AD) identity provider\n\tIdentityProviderTypeEntra IdentityProviderType = \"entra\"\n\t// IdentityProviderTypeGoogle is the Google identity provider\n\tIdentityProviderTypeGoogle IdentityProviderType = \"google\"\n\t// IdentityProviderTypeOkta is the Okta identity provider\n\tIdentityProviderTypeOkta IdentityProviderType = \"okta\"\n\t// IdentityProviderTypePocketID is the PocketID identity provider","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/server/types/identity_provider.go#L1-L34","documentation":"Sentinel error types.ErrIdentityProviderIssuerMismatch (management/server/types/identity_provider.go:16), returned by validateOIDCIssuer when the discovery document was fetched successfully but its top-level \"issuer\" claim differs from the configured Issuer string. Per OIDC Discovery (RFC 8414 section 3), the provider must advertise its own issuer at that URL, and NetBird enforces exact string equality.","triggerScenarios":"Configured issuer \"https://auth.example.com/realms/nb\" while the discovery document at that URL advertises \"https://auth.example.com/realms/nb/\" (trailing slash), a proxy-rewritten host, or a completely different external URL. The error message includes expected vs got.","commonSituations":"Reverse proxy in front of the IdP rewrites the Host header or strips path prefixes; IdP behind a public domain while advertising its internal URL (or vice versa); trailing-slash differences between what you configure and what the provider advertises; multi-tenant realms with canonical URLs.","solutions":["Read the error's expected/got values and configure the Issuer exactly as the provider advertises it in its discovery document","Fix the proxy/IdP so the advertised issuer matches the URL the discovery document is served under (X-Forwarded-Host / Forwarded headers)","Remove or add the trailing slash so both strings are byte-identical"],"exampleFix":"// before\nidp.Issuer = \"https://auth.example.com/realms/nb\"  // provider advertises .../realms/nb/\n\n// after\nidp.Issuer = \"https://auth.example.com/realms/nb/\"","handlingStrategy":"validation","validationCode":"// Verify the provider advertises exactly this issuer before saving\nfunc issuerMatchesDiscovery(ctx context.Context, issuer string) error {\n    if err := issuerReachable(ctx, issuer); err != nil { return err }\n    body, _ := fetchDiscovery(ctx, issuer)\n    var doc struct{ Issuer string `json:\"issuer\"` }\n    if err := json.Unmarshal(body, &doc); err != nil { return err }\n    if doc.Issuer != issuer {\n        return fmt.Errorf(\"provider advertises %q; use that as the issuer\", doc.Issuer)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"err := saveIdP(ctx, idp)\nif err != nil && errors.Is(err, types.ErrIdentityProviderIssuerMismatch) {\n    // parse 'expected X got Y' from the message and re-save with Y as issuer\n}","preventionTips":["Always copy the issuer from the provider's discovery document, not from its admin UI URL","Configure reverse proxies (X-Forwarded-Proto/Host) so the advertised issuer matches the external URL","Watch for trailing-slash differences; OIDC requires byte-exact issuer equality"],"tags":["management","identity-provider","oidc","discovery","validation"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}