# WeKnora Principal Model for API Keys and MCP/Embed Session Isolation

> Discover WeKnora's principal model for API key and session isolation. It uses typed identifiers to separate human and machine authentication, securing your API keys, tokens, and embed sessions.

- Repository: [Tencent/WeKnora](https://github.com/tencent/WeKnora)
- Tags: architecture
- Published: 2026-09-12

---

**WeKnora separates human and machine authentication through a lightweight principal model that isolates API keys, MCP OAuth tokens, and embed sessions by representing every caller as a typed identifier stored in the request context.**

The Tencent/WeKnora repository implements a robust authorization layer that treats API keys, embed visitors, and instant-messaging users as distinct security principals. This principal model for API keys and MCP/embed session isolation ensures that machine credentials cannot impersonate human users and that OAuth tokens remain scoped to their specific caller identity.

## What Is the Principal Model?

A **principal** is a lightweight struct representing the ultimate caller of a request, deliberately separate from the traditional `UserID`. While human users authenticate via JWT, many callers—such as API keys, IM integrations, and anonymous embed visitors—lack a regular WeKnora account. The principal model provides a unified way to identify these disparate callers through a simple type-plus-identifier tuple.

### Principal Types and Structure

The core enumeration lives in [`internal/types/principal.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/principal.go):

```go
type Principal struct {
    Type string
    ID   string
}

```

WeKnora defines eight distinct principal types:

- **web_user**: Normal logged-in human user (JWT-based).
- **api_tenant**: Tenant-scoped API-key principal issued per-key.
- **api_platform**: Platform-wide API-key principal for system keys.
- **api_external_user**: API-key mapped to an external user identity.
- **im_user**: Instant-messaging user (e.g., Feishu, WeChat).
- **embed_channel**: Configuration of a public embed channel.
- **embed_session**: Concrete chat session for an embed visitor.
- **embed_visitor**: Anonymous browser visitor that may open multiple sessions.

The principal is attached to a request context via `WithPrincipal(ctx, p)` and retrieved via `PrincipalFromContext`. If no principal exists, the system falls back to the JWT `UserID` and treats it as a `web_user`.

## API Key Isolation via First-Class Principals

API keys are **first-class principals** of type `api_tenant` or `api_platform`. When a request carries the `X-API-Key` header, middleware extracts a `TenantAPIKeyScope` and stores it in the context.

### The API Key Gate

The **API key gate** in [`internal/middleware/api_key_gate.go`](https://github.com/Tencent/WeKnora/blob/main/internal/middleware/api_key_gate.go) evaluates every request against declared `APIKeyRoutePolicy` rules:

```go
func (a *APIKeyRouteAuthorizer) authorize(
    scope types.TenantAPIKeyScope, method, fullPath string) error {

    policy, ok := a.Lookup(method, fullPath)
    if !ok { return errTenantAPIKeyScopeForbidden }      // default deny
    if policy.PlatformOnly && !scope.IsPlatform() { … } // platform-only routes
    if scope.FullAccess { return nil }                  // full-access keys
    for _, cap := range policy.Capabilities {
        if cap != "" && scope.HasCapability(cap) { return nil }
    }
    …
}

```

This implementation enforces three isolation guarantees:

1. **Default deny**: Routes without an explicit policy are implicitly rejected, eliminating the "forget-to-add-APIKeyDeny" vulnerability.
2. **Platform-only enforcement**: Routes marked `policy.PlatformOnly` reject tenant-scoped keys.
3. **Capability-based access**: Fine-grained permissions via `policy.Capabilities` allow keys to possess specific abilities (e.g., `retrieve`, `modify`) rather than blanket access.

The authorizer attaches as the first handler of the `/api/v1` group, ensuring every API key request is validated before business logic executes.

## MCP OAuth Token Isolation

MCP services requiring OAuth tokens store credentials per-principal rather than per-user. The token store uses the composite key `(tenant_id, principal_type, principal_id, service_id)` as defined in migration [`migrations/versioned/000064_principal_model.up.sql`](https://github.com/Tencent/WeKnora/blob/main/migrations/versioned/000064_principal_model.up.sql).

For a standard API key call, the principal is `api_tenant`; for embed flows, it becomes an `embed_visitor` or `embed_session` principal derived from the `X-Embed-Visitor` header. The function `MCPOAuthPrincipalFromContext` in [`internal/types/principal.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/principal.go) resolves the correct identity:

```go
func MCPOAuthPrincipalFromContext(ctx context.Context) Principal {
    p, ok := PrincipalFromContext(ctx)
    if !ok { return Principal{} }
    if p.Type != PrincipalEmbedSession { return p }
    // embed session → embed visitor if X-Embed-Visitor present
    …
}

```

This design prevents cross-visitor token leakage by ensuring each embed visitor maintains distinct OAuth credentials.

## Embed Session Isolation

Embedding operates through a public **embed channel** (`embed_channel`). When a visitor opens the widget, the frontend sends an `X-Embed-Visitor` header containing a random visitor ID. The server then materializes two distinct principals:

- **Embed session principal** (`embed_session`): Identifies a single chat session via `tenant:channel:session`.
- **Embed visitor principal** (`embed_visitor`): Groups all sessions belonging to the same anonymous browser.

### Session Ownership and Validation

The `SessionOwnerIDFromContext` function in [`internal/types/principal.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/principal.go) constructs ownership identifiers such as `api_tenant_key:<tenantID>:<keyID>` for API key sessions or `embed_session:<...>` for embed sessions.

The embed authentication middleware in [`internal/middleware/embed_auth.go`](https://github.com/Tencent/WeKnora/blob/main/internal/middleware/embed_auth.go) validates the `X-Embed-Visitor` header, extracts the visitor principal, and enforces rate limits and origin checks before allowing any embed API call.

## Summary

- The **principal model** unifies human and machine authentication through a type-plus-ID struct stored in the request context.
- **API keys** isolate tenant and platform access via the `APIKeyRouteAuthorizer` in [`internal/middleware/api_key_gate.go`](https://github.com/Tencent/WeKnora/blob/main/internal/middleware/api_key_gate.go), which implements default-deny policies and capability-based authorization.
- **MCP OAuth tokens** are scoped to specific principals using composite database keys, preventing token reuse across API keys and embed visitors.
- **Embed sessions** use separate principals for channels, sessions, and visitors, ensuring anonymous browser identities cannot interfere with tenant data or other visitors.
- The system stores principal metadata in `mcp_oauth_tokens` and `api_principal_config` tables as defined in migration [`000064_principal_model.up.sql`](https://github.com/Tencent/WeKnora/blob/main/000064_principal_model.up.sql).

## Frequently Asked Questions

### How does WeKnora prevent API keys from impersonating human users?

WeKnora stores API keys as distinct principal types (`api_tenant` or `api_platform`) rather than converting them to `web_user` identities. The `APIKeyRouteAuthorizer` middleware evaluates these principals against explicit route policies before any handler executes, ensuring API keys can only access capabilities explicitly granted to them and cannot masquerade as JWT-authenticated humans.

### What happens if a route lacks an API key policy definition?

Routes without a registered `APIKeyRoutePolicy` trigger an implicit denial. The `authorize` function in [`internal/middleware/api_key_gate.go`](https://github.com/Tencent/WeKnora/blob/main/internal/middleware/api_key_gate.go) returns `errTenantAPIKeyScopeForbidden` when no policy is found, implementing a fail-closed security posture that prevents accidental exposure of endpoints to API key access.

### How are MCP OAuth tokens isolated between different embed visitors?

Each embed visitor receives a unique `embed_visitor` principal derived from the `X-Embed-Visitor` header. The `MCPOAuthPrincipalFromContext` function resolves this identity when storing or retrieving tokens, using the composite database key `(tenant_id, principal_type, principal_id, service_id)`. This ensures visitor A's OAuth credentials remain inaccessible to visitor B even when both interact with the same embed channel.

### Can a single API key support both tenant-scoped and platform-wide operations?

No, API keys are categorized as either `api_tenant` or `api_platform` principals at creation. Platform-only routes explicitly reject tenant-scoped keys by checking `policy.PlatformOnly` and `scope.IsPlatform()`. Keys requiring broad access must be issued as `api_platform` principals, subject to stricter security controls.