{"record":{"id":"6f286f77fdda1c3c","repo":"juanfont/headscale","slug":"creating-oidc-provider-from-issuer-config-w","errorCode":null,"errorMessage":"creating OIDC provider from issuer config: %w","messagePattern":"creating OIDC provider from issuer config: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/oidc.go","lineNumber":92,"sourceCode":"\t// steps. It is a bounded [expirable.LRU] keyed by OIDC state, evicting oldest\n\t// entries to keep the cache footprint constant under attack.\n\tauthCache *expirable.LRU[string, AuthInfo]\n\n\toidcProvider *oidc.Provider\n\toauth2Config *oauth2.Config\n}\n\nfunc NewAuthProviderOIDC(\n\tctx context.Context,\n\th *Headscale,\n\tserverURL string,\n\tcfg *types.OIDCConfig,\n) (*AuthProviderOIDC, error) {\n\t// Use the caller's context (bounded, see app.go) so a slow or unreachable\n\t// issuer fails discovery within the timeout instead of hanging startup.\n\toidcProvider, err := oidc.NewProvider(ctx, cfg.Issuer)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating OIDC provider from issuer config: %w\", err)\n\t}\n\n\toauth2Config := &oauth2.Config{\n\t\tClientID:     cfg.ClientID,\n\t\tClientSecret: cfg.ClientSecret,\n\t\tEndpoint:     oidcProvider.Endpoint(),\n\t\tRedirectURL:  strings.TrimSuffix(serverURL, \"/\") + \"/oidc/callback\",\n\t\tScopes:       cfg.Scope,\n\t}\n\n\tauthCache := expirable.NewLRU[string, AuthInfo](\n\t\tauthCacheMaxEntries,\n\t\tnil,\n\t\tauthCacheExpiration,\n\t)\n\n\treturn &AuthProviderOIDC{\n\t\th:         h,","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/oidc.go#L74-L110","documentation":"Returned by NewAuthProviderOIDC when oidc.NewProvider fails to discover the OIDC issuer's well-known configuration (fetching {issuer}/.well-known/openid-configuration and validating it). This runs at startup with the caller's bounded context, so an unreachable or misbehaving issuer fails fast rather than hanging.","triggerScenarios":"oidc.issuer URL wrong or not reachable (DNS failure, TLS error, timeout); the issuer's discovery document is invalid or serves HTML (e.g. behind an identity provider login page); clock skew breaking TLS validation; the issuer does not implement discovery.","commonSituations":"Typo in the oidc.issuer config value; self-signed issuer certificate without the CA trusted by headscale; network egress blocked from the headscale host; provider outage; issuer URL with a trailing path mismatch (discovery requires exact issuer-string match).","solutions":["curl the discovery endpoint from the headscale host: curl -v https://issuer.example.com/.well-known/openid-configuration","Fix the issuer URL to exactly the value the provider documents (scheme, host, path, no trailing slash)","Install/trust the issuer's CA certificate if it uses a private PKI","Check the oidc.timeout relevant egress/firewall rules and DNS resolution","Verify the discovery document's 'issuer' field matches the configured issuer string exactly"],"exampleFix":"# before\noidc:\n  issuer: https://sso.example.com/oauth2/\n\n# after\noidc:\n  issuer: https://sso.example.com/oauth2","handlingStrategy":"retry","validationCode":"discURL := strings.TrimSuffix(cfg.Issuer, \"/\") + \"/.well-known/openid-configuration\"\nresp, err := http.Get(discURL) // with timeout\nif err != nil || resp.StatusCode != 200 {\n    return fmt.Errorf(\"OIDC issuer discovery unreachable at %s: %v\", discURL, err)\n}","typeGuard":null,"tryCatchPattern":"oidcProvider, err := oidc.NewProvider(ctx, cfg.Issuer)\nif err != nil {\n    // startup error: log issuer URL and wrapped cause, fix config/network, restart\n    return nil, fmt.Errorf(\"creating OIDC provider from issuer config: %w\", err)\n}","preventionTips":["Smoke-test the discovery endpoint from the headscale host before enabling OIDC","Pin the issuer URL exactly as documented by the provider, and trust its CA in the host store","Keep OIDC issuer dependencies in retry-backed startup probes so transient provider outages don't strand deployments"],"tags":["oidc","sso","config","network","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}