{"record":{"id":"4d1671d82043b58d","repo":"siyuan-note/siyuan","slug":"oidc-authorization-code-is-missing","errorCode":null,"errorMessage":"OIDC authorization code is missing","messagePattern":"OIDC authorization code is missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc.go","lineNumber":843,"sourceCode":"\t}\n\tif err := authenticateOIDCSession(c, transaction.RememberMe); err != nil {\n\t\treturn writeOIDCCallbackPage(c, false, oidcUserMessage())\n\t}\n\treturn apicontract.RedirectHTTPContent(http.StatusFound, safeOIDCRedirectTarget(transaction.To))\n}\n\nfunc cleanupOIDCTransactionsLocked() {\n\tnow := time.Now()\n\tfor state, transaction := range oidcTransactions.byState {\n\t\tif now.After(transaction.ExpiresAt) {\n\t\t\tdeleteOIDCTransactionLocked(state)\n\t\t}\n\t}\n}\n\nfunc finishOIDCExchange(c *gin.Context, transaction *oidcTransaction, code string) error {\n\tif code == \"\" {\n\t\treturn errors.New(\"OIDC authorization code is missing\")\n\t}\n\tconfig := Conf.GetOIDC()\n\tprovider := transaction.Provider\n\tif transaction.Flow == oidcFlowValidate {\n\t\tif transaction.Config == nil || provider == nil {\n\t\t\treturn errors.New(\"OIDC validation configuration is missing\")\n\t\t}\n\t\tconfig = transaction.Config\n\t} else {\n\t\tvar err error\n\t\tprovider, err = getOIDCProvider(c.Request.Context(), transaction.RedirectURL)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\texchangeContext, cancel := context.WithTimeout(c.Request.Context(), oidcExchangeTimeout)\n\tdefer cancel()\n\tclaims, err := provider.Exchange(exchangeContext, code, transaction.CodeVerifier, transaction.Nonce)","sourceCodeStart":825,"sourceCodeEnd":861,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc.go#L825-L861","documentation":"finishOIDCExchange completes the OAuth2/OIDC authorization-code exchange. The OAuth2 authorization-code grant requires the code query parameter returned by the provider; if code is empty the exchange cannot proceed, so the handler rejects it immediately. Providers send an error parameter instead of code on failures, which can surface as an empty code.","triggerScenarios":"OIDCCallback or OIDCMobileCallback invoked without ?code= — e.g. the provider redirected back with an error= parameter (user denied consent), the callback URL was opened directly/manually, or the frontend invoked the mobile callback without forwarding the code.","commonSituations":"User cancels at the provider's consent screen and the provider redirects back without a code; misconfigured redirect URI causing a provider error redirect; a browser bookmark of the callback URL; network-truncated redirect dropping query params.","solutions":["Check the callback request for error/error_description query params and show the provider's message to the user instead of retrying","Re-initiate login from the beginning (new authorize URL / poll token) so the provider issues a fresh authorization code","Verify the redirect URI and client configuration at the provider so normal logins return a code","Do not reuse or replay old callback URLs — codes are single-use and short-lived"],"exampleFix":"// before\n// callback URL: /api/oidc/callback  (no code param) -> error\n// after\n// handle provider error redirect first\nif c.Query(\"error\") != \"\" { renderAuthError(c, c.Query(\"error_description\")); return }\nerr := model.OIDCCallback(c, c.Query(\"code\"))","handlingStrategy":"validation","validationCode":"// before invoking the callback handler, ensure the code param exists\nif c.Query(\"code\") == \"\" && c.Query(\"error\") != \"\" { renderProviderError(c, c.Query(\"error_description\")); return }","typeGuard":null,"tryCatchPattern":"if err := model.OIDCCallback(c, code); err != nil && strings.Contains(err.Error(), \"authorization code is missing\") {\n    renderAuthError(c, \"Login was cancelled or the provider returned no code; please retry\")\n}","preventionTips":["Handle the provider's error= redirect explicitly instead of letting it hit the code path","Never bookmark or replay callback URLs","Verify redirect URI configuration at the IdP so error redirects are distinguishable"],"tags":["oidc","oauth2","authorization-code","callback"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}