{"record":{"id":"18b33a63b735dcc2","repo":"netbirdio/netbird","slug":"s-s","errorCode":null,"errorMessage":"%s\n\n    %s\n","messagePattern":"%s\n\n    %s\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/daemon_error.go","lineNumber":21,"sourceCode":"import (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"google.golang.org/genproto/googleapis/rpc/errdetails\"\n\tgstatus \"google.golang.org/grpc/status\"\n\n\t\"github.com/netbirdio/netbird/client/internal/ipcauth\"\n)\n\n// daemonCallError prepares a daemon error for display. A refusal the daemon\n// raised because the operation needs root/administrator is already guidance\n// written for the user, so it is surfaced on its own instead of buried under the\n// gRPC envelope and the name of the RPC that hit it. Anything else is wrapped\n// with context as usual.\nfunc daemonCallError(context string, err error) error {\n\tif guidance, ok := privilegeGuidance(err); ok {\n\t\treturn errors.New(guidance)\n\t}\n\treturn fmt.Errorf(\"%s: %w\", context, err)\n}\n\n// privilegeGuidance renders the daemon's privilege refusal as a summary and the\n// command that performs the operation with the privileges it needs. It reports\n// false for any other error.\nfunc privilegeGuidance(err error) (string, bool) {\n\tinfo, ok := privilegeErrorInfo(err)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\n\tsummary := info.GetMetadata()[ipcauth.ErrorMetaSummary]\n\tcommand := info.GetMetadata()[ipcauth.ErrorMetaCommand]\n\tif summary == \"\" {\n\t\t// Detail without a summary: fall back to the status message, which\n\t\t// carries the same text.","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/daemon_error.go#L3-L39","documentation":"Raised in the management service's proxy OAuth callback handler (management/server/http/handlers/proxy/auth.go:92) when golang.org/x/oauth2's Config.Exchange fails while swapping the authorization code for tokens at the IdP token endpoint. The oauth2.Config here carries only ClientID, RedirectURL, and the provider endpoint (no ClientSecret), so Exchange is a public-client PKCE request; any non-2xx IdP response (invalid_grant, invalid_client, invalid_redirect_uri) or transport error surfaces here and the handler answers 500.","triggerScenarios":"GET on types.ProxyCallbackEndpoint after ValidateState succeeded, but the IdP rejects the exchange: authorization code expired or already redeemed (callback URL reloaded), PKCE code_verifier from ValidateState does not match the challenge sent at authorize time, RedirectURL (oidcConfig.CallbackURL) differs from the URI used in the authorize request or registered in the IdP app, the IdP app is confidential and demands a client_secret this config never sends, or the management host cannot reach the token endpoint (TLS, DNS, egress firewall).","commonSituations":"management.json proxy OIDC callback URL not matching the IdP-registered redirect exactly (scheme, port, path); user double-visits or refreshes the callback link; management container has no egress to the IdP; IdP rotated client credentials while management still holds the old ClientID; reverse proxy in front of management rewrites the callback path.","solutions":["Read the wrapped error in the management log (log.WithError at the same line): 'invalid_grant' means expired/reused code (restart the login flow), 'invalid_client' means the IdP expects a confidential client or the ClientID is wrong, 'invalid_request/redirect_uri' means a URI mismatch.","Make oidcConfig.CallbackURL identical, character for character, to the redirect URI registered in the IdP application and to the one used when the authorize URL was built.","Verify egress from the management host: curl -v the IdP token endpoint from inside the management container/process environment.","If the flow was interrupted (state older than the code TTL or page refresh), restart from the proxy domain so a fresh code + verifier pair is generated."],"exampleFix":"// before: IdP app configured as confidential, exchange built without secret\ntoken, err := (&oauth2.Config{\n    ClientID:    oidcConfig.ClientID,\n    Endpoint:    provider.Endpoint(),\n    RedirectURL: oidcConfig.CallbackURL,\n}).Exchange(r.Context(), r.URL.Query().Get(\"code\"), oauth2.VerifierOption(codeVerifier))\n\n// after: register the IdP app as public/PKCE (no secret), or if confidential,\n// send the secret and keep the callback URL byte-identical to the IdP registration:\ntoken, err := (&oauth2.Config{\n    ClientID:     oidcConfig.ClientID,\n    ClientSecret: oidcConfig.ClientSecret, // only for confidential clients\n    Endpoint:     provider.Endpoint(),\n    RedirectURL:  oidcConfig.CallbackURL,   // must equal the registered redirect URI\n}).Exchange(r.Context(), r.URL.Query().Get(\"code\"), oauth2.VerifierOption(codeVerifier))","handlingStrategy":"validation","validationCode":"// Preflight from the management host before driving users through the flow:\n// 1) issuer discovery answers, 2) callback URL matches the registered one.\nprovider, err := oidc.NewProvider(ctx, cfg.Issuer)\nif err != nil {\n    return fmt.Errorf(\"issuer unreachable: %w\", err)\n}\n_ = provider.Endpoint() // forces well-known fetch\nif cfg.CallbackURL == \"\" || !strings.HasPrefix(cfg.CallbackURL, \"https://\") {\n    return errors.New(\"proxy OIDC callback URL must be set and use https\")\n}\n// Compare cfg.CallbackURL against the IdP app registration before starting login.","typeGuard":null,"tryCatchPattern":"token, err := oauth2Cfg.Exchange(ctx, code, oauth2.VerifierOption(verifier))\nif err != nil {\n    var rErr *oauth2.RetrieveError\n    if errors.As(err, &rErr) {\n        // rErr.Code / body carry the IdP's error: invalid_grant, invalid_client...\n        log.Printf(\"idp rejected exchange: %s %s\", rErr.Code, rErr.Body)\n    }\n    return fmt.Errorf(\"exchange code: %w\", err)\n}","preventionTips":["Keep the IdP-registered redirect URI and oidcConfig.CallbackURL byte-identical; include scheme, port, and path.","Register the IdP application as a public PKCE client since the exchange sends no client secret.","Never bookmark or reload the callback URL; always restart login from the proxy domain for a fresh code+verifier.","Monitor management logs for 'Failed to exchange code for token' and alert on bursts — they usually mean config drift."],"tags":["oidc","oauth2","pkce","management","http"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}