Tencent/WeKnora · error

principal context is required to authorize OAuth MCP service

Error message

principal context is required to authorize OAuth MCP service %s

What it means

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.

Source

Thrown at internal/mcp/oauth_manager.go:94

}

// StartAuthorization performs discovery + (one-time) dynamic client
// registration, then returns the authorization URL and an opaque attempt ID.
// redirectURI is the backend callback URL registered with the auth server;
// frontendRedirect is where the callback bounces the browser when finished.
func (m *OAuthManager) StartAuthorization(
	ctx context.Context,
	service *types.MCPService,
	tenantID uint64,
	principal types.Principal,
	redirectURI, frontendRedirect string,
) (authorizationURL, attemptID string, err error) {
	if !service.AuthConfig.IsOAuth() {
		return "", "", fmt.Errorf("MCP service %s does not use OAuth", service.ID)
	}
	principal = principal.Normalize()
	if !principal.Valid() {
		return "", "", fmt.Errorf("principal context is required to authorize OAuth MCP service %s", service.ID)
	}

	h, err := m.newHandler(ctx, service, tenantID, principal, redirectURI)
	if err != nil {
		return "", "", err
	}

	// Register a client dynamically if we don't have one yet for this service.
	existing, _ := m.repo.GetClient(ctx, tenantID, service.ID)
	if existing == nil {
		if err := h.RegisterClient(ctx, clientRegistrationName); err != nil {
			return "", "", fmt.Errorf("dynamic client registration failed: %w", err)
		}
		clientID := h.GetClientID()
		if clientID == "" {
			return "", "", fmt.Errorf("dynamic client registration returned an empty client_id")
		}
		if err := m.repo.SaveClient(ctx, &types.MCPOAuthClient{

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Pass the authenticated user's principal from the request context into StartAuthorization
  2. Ensure auth middleware injects a valid principal before handlers call the OAuth manager
  3. For machine flows, construct a principal with a valid service identity rather than a zero value
  4. Check that Normalize()+Valid() requirements (non-zero tenant/user IDs) are met

Example fix

// before
_, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, types.Principal{}, redirect, "")
// after
principal, ok := types.PrincipalFromContext(ctx)
if !ok || !principal.Normalize().Valid() {
    return fmt.Errorf("unauthenticated request: cannot start OAuth flow")
}
_, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, principal, redirect, "")
Defensive patterns

Strategy: validation

Validate before calling

principal, ok := types.PrincipalFromContext(ctx)
if !ok || !principal.Normalize().Valid() {
    return fmt.Errorf("authenticated principal required")
}

Type guard

func hasValidPrincipal(ctx context.Context) bool {
    p, ok := types.PrincipalFromContext(ctx)
    return ok && p.Normalize().Valid()
}

Try / catch

if !hasValidPrincipal(ctx) { return http.ErrNoAuth /* redirect to login */ }
principal, _ := types.PrincipalFromContext(ctx)
if _, _, err := mgr.StartAuthorizationForService(ctx, svc, tenantID, principal, redirect, ""); err != nil {
    if strings.Contains(err.Error(), "principal context is required") { return redirectLogin }
    return err
}

Prevention

When it happens

Trigger: Calling AuthorizeURL/StartAuthorizationForService with a zero-value types.Principal, or one that fails principal.Valid() after Normalize() (no user/org identity in context).

Common situations: 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.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/9fcce0b21fbd676d. Report an issue: GitHub.