{"record":{"id":"9fcce0b21fbd676d","repo":"Tencent/WeKnora","slug":"principal-context-is-required-to-authorize-oauth-m","errorCode":null,"errorMessage":"principal context is required to authorize OAuth MCP service %s","messagePattern":"principal context is required to authorize OAuth MCP service (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/mcp/oauth_manager.go","lineNumber":94,"sourceCode":"}\n\n// StartAuthorization performs discovery + (one-time) dynamic client\n// registration, then returns the authorization URL and an opaque attempt ID.\n// redirectURI is the backend callback URL registered with the auth server;\n// frontendRedirect is where the callback bounces the browser when finished.\nfunc (m *OAuthManager) StartAuthorization(\n\tctx context.Context,\n\tservice *types.MCPService,\n\ttenantID uint64,\n\tprincipal types.Principal,\n\tredirectURI, frontendRedirect string,\n) (authorizationURL, attemptID string, err error) {\n\tif !service.AuthConfig.IsOAuth() {\n\t\treturn \"\", \"\", fmt.Errorf(\"MCP service %s does not use OAuth\", service.ID)\n\t}\n\tprincipal = principal.Normalize()\n\tif !principal.Valid() {\n\t\treturn \"\", \"\", fmt.Errorf(\"principal context is required to authorize OAuth MCP service %s\", service.ID)\n\t}\n\n\th, err := m.newHandler(ctx, service, tenantID, principal, redirectURI)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\t// Register a client dynamically if we don't have one yet for this service.\n\texisting, _ := m.repo.GetClient(ctx, tenantID, service.ID)\n\tif existing == nil {\n\t\tif err := h.RegisterClient(ctx, clientRegistrationName); err != nil {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"dynamic client registration failed: %w\", err)\n\t\t}\n\t\tclientID := h.GetClientID()\n\t\tif clientID == \"\" {\n\t\t\treturn \"\", \"\", fmt.Errorf(\"dynamic client registration returned an empty client_id\")\n\t\t}\n\t\tif err := m.repo.SaveClient(ctx, &types.MCPOAuthClient{","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/mcp/oauth_manager.go#L76-L112","documentation":"Returned by StartAuthorization when the principal is missing or invalid after normalization. OAuth tokens are stored per-principal (tenant + principal identify the token row), so an anonymous/system context without a valid principal cannot start an authorization flow.","triggerScenarios":"Calling AuthorizeURL/StartAuthorizationForService with a zero-value types.Principal, or one that fails principal.Valid() after Normalize() (no user/org identity in context).","commonSituations":"Background job or webhook handler calls the manager without propagating the user's auth context; middleware dropped the principal from the request context; testing with a bare Principal{} struct.","solutions":["Pass the authenticated user's principal from the request context into StartAuthorization","Ensure auth middleware injects a valid principal before handlers call the OAuth manager","For machine flows, construct a principal with a valid service identity rather than a zero value","Check that Normalize()+Valid() requirements (non-zero tenant/user IDs) are met"],"exampleFix":"// before\n_, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, types.Principal{}, redirect, \"\")\n// after\nprincipal, ok := types.PrincipalFromContext(ctx)\nif !ok || !principal.Normalize().Valid() {\n    return fmt.Errorf(\"unauthenticated request: cannot start OAuth flow\")\n}\n_, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, principal, redirect, \"\")","handlingStrategy":"validation","validationCode":"principal, ok := types.PrincipalFromContext(ctx)\nif !ok || !principal.Normalize().Valid() {\n    return fmt.Errorf(\"authenticated principal required\")\n}","typeGuard":"func hasValidPrincipal(ctx context.Context) bool {\n    p, ok := types.PrincipalFromContext(ctx)\n    return ok && p.Normalize().Valid()\n}","tryCatchPattern":"if !hasValidPrincipal(ctx) { return http.ErrNoAuth /* redirect to login */ }\nprincipal, _ := types.PrincipalFromContext(ctx)\nif _, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, principal, redirect, \"\"); err != nil {\n    if strings.Contains(err.Error(), \"principal context is required\") { return redirectLogin }\n    return err\n}","preventionTips":["Require auth middleware on every route that starts OAuth flows","Never construct zero-value principals for user-facing flows","Unit-test handlers with and without principal context","Fail fast at the handler boundary, before calling the OAuth manager"],"tags":["oauth","authentication","principal","validation"],"backgroundTag":"missing-auth-context","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}