{"record":{"id":"2f602cf221e67a14","repo":"Tencent/WeKnora","slug":"oauth-authorization-attempt-does-not-match-the-cur","errorCode":null,"errorMessage":"oauth authorization attempt does not match the current principal or service","messagePattern":"oauth authorization attempt does not match the current principal or service","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/mcp/oauth_manager.go","lineNumber":245,"sourceCode":"\n// IsAuthorizationAttemptComplete reports whether this exact authorization\n// attempt completed for the requested principal and service. A pre-existing\n// token must never satisfy a newly opened OAuth popup.\nfunc (m *OAuthManager) IsAuthorizationAttemptComplete(\n\tctx context.Context,\n\ttenantID uint64,\n\tprincipal types.Principal,\n\tserviceID, attemptID string,\n) (bool, error) {\n\tattempt, err := m.states.Attempt(ctx, attemptID)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tprincipal = principal.Normalize()\n\tattemptPrincipal := attempt.Principal.Normalize()\n\tif attempt.TenantID != tenantID || attempt.ServiceID != serviceID ||\n\t\tattemptPrincipal.Type != principal.Type || attemptPrincipal.ID != principal.ID {\n\t\treturn false, fmt.Errorf(\"oauth authorization attempt does not match the current principal or service\")\n\t}\n\treturn attempt.Completed, nil\n}\n\n// AuthorizationStatus reports whether the stored access token is usable now,\n// or is expired but still has a refresh token that runtime use can rotate.\nfunc (m *OAuthManager) AuthorizationStatus(\n\tctx context.Context, tenantID uint64, principal types.Principal, serviceID string,\n) (OAuthAuthorizationStatus, error) {\n\ttok, err := m.repo.GetTokenForPrincipal(ctx, tenantID, principal, serviceID)\n\tif err != nil {\n\t\treturn OAuthAuthorizationStatus{}, err\n\t}\n\treturn tokenStatus(tok, time.Now()), nil\n}\n\n// IsAuthorized reports whether the given principal has an access token that is\n// usable now. An expired row is not authorization success merely because its","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/mcp/oauth_manager.go#L227-L263","documentation":"IsAuthorizationAttemptComplete verifies that the stored OAuth authorization attempt belongs to the tenant, service, and principal making the status query. This error is thrown when the attempt record's tenant/service IDs or normalized principal (type + ID) do not match the caller's current identity. It prevents a principal from reading or consuming another principal's OAuth flow state.","triggerScenarios":"Calling Status (or TestAuthorizationAttemptStatusIsScopedToPrincipalAndService) with a state parameter whose stored OAuthAttempt was created by a different principal ID/type, a different tenantID, or a different serviceID.","commonSituations":"A user re-logs into the app with a different account before completing OAuth; the principal identity rotated (e.g. token refresh assigned a new user ID); the state token is replayed against another tenant/service; multitenant routing sent the callback to the wrong tenant context.","solutions":["Verify the principal passed to IsAuthorizationAttemptComplete is exactly the one that started the authorization attempt (same type and ID after Normalize()).","Start a fresh authorization attempt for the current principal instead of reusing the old state value.","Check tenant/service routing so the callback/status call lands in the same tenantID/serviceID context that created the attempt.","If identities legitimately change, migrate or delete the stale attempt rather than querying it under the new principal."],"exampleFix":"// before\ncomplete, err := svc.IsAuthorizationAttemptComplete(ctx, tenantA, serviceID, oldPrincipal, state)\n// after\ncomplete, err := svc.IsAuthorizationAttemptComplete(ctx, attempt.TenantID, attempt.ServiceID, attempt.Principal, state)","handlingStrategy":"try-catch","validationCode":"if attempt.TenantID != tenantID || attempt.ServiceID != serviceID || attempt.Principal.Normalize().ID != principal.Normalize().ID {\n    // skip status check; start a new attempt\n}","typeGuard":"func attemptBelongsTo(attempt OAuthAttempt, tenantID, serviceID string, principal Principal) bool {\n    p := principal.Normalize()\n    ap := attempt.Principal.Normalize()\n    return attempt.TenantID == tenantID && attempt.ServiceID == serviceID && ap.Type == p.Type && ap.ID == p.ID\n}","tryCatchPattern":"complete, err := svc.IsAuthorizationAttemptComplete(ctx, tenantID, serviceID, principal, state)\nif err != nil {\n    // principal/service mismatch: discard state, start a fresh OAuth attempt\n    state = beginNewAuthorizationAttempt(ctx, tenantID, serviceID, principal)\n}","preventionTips":["Always pass the exact principal that initiated the attempt; re-read it from the session, not from request input.","Normalize principals on both write and read paths before comparison.","Scope state cookies to tenant+principal so a mismatched identity cannot even submit the state.","Rotate session identity carefully: invalidate outstanding OAuth attempts on login/logout."],"tags":["oauth","authorization","principal-mismatch","security"],"backgroundTag":"oauth-principal-mismatch","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}