# Authentication > Module 06 · Plane: Data · Roadmap phase: 3 > Part of [MCP Nexus architecture](../ARCHITECTURE.md). ## Purpose Authentication turns an inbound connection at the [API Gateway](04-gateway.md) into a **verified principal** — a stable identity for either a human user (dashboard/API operator) or an AI agent (Claude, Cursor, …). It answers exactly one question: *"Who is calling?"* It does **not** decide what they may do; that is [RBAC](08-rbac.md). AuthN is the seam through which every request passes before it can be scoped, routed, or audited (Architecture §5, §9). It is pluggable by tenet #3: API keys first (Phase 3), OAuth2/OIDC and JWT sessions next, and LDAP/Active Directory/SAML (enabling SSO) later. ## Responsibilities - Own a set of pluggable **AuthN providers** that each attempt to resolve request credentials into a `Principal`. Starter provider: **API keys**. Follow-ons: OAuth2, OpenID Connect (authorization-code + PKCE), JWT session validation, then LDAP, Active Directory, and SAML (SSO/enterprise IdP federation). - Extract credentials from the request in a provider-appropriate way: `Authorization: Bearer `, `X-API-Key`, a session cookie, or an MCP `initialize` handshake carrying an agent credential. - Establish and validate **sessions**: mint short-lived JWT access tokens (with a refresh path) after an interactive login; validate their signature, expiry, audience, and issuer on every request. - Issue, hash, store, list, and **revoke API keys**; keys are stored only as salted hashes, shown in plaintext exactly once at creation. - Attach the resolved `Principal` (with its provider, credential id, and asserted identity claims) to the request context so downstream RBAC, Router, and Audit all read one canonical identity. - Map an agent's presented credential to its [AgentProfile](14-agent-profiles.md) identity so profile→role resolution can proceed. - Emit an audit event for every authentication decision — success, failure, and revocation — with principal, provider, and source address (Architecture §9). ## Non-goals - **Authorization.** Deciding which tools/namespaces a principal may see or call is [RBAC](08-rbac.md); this module only proves identity. - **The agent-facing identity binding.** Modeling per-agent policy objects is [AI Agent Profiles](14-agent-profiles.md); AuthN merely resolves a credential to the profile's identity. - **Credential storage for upstream services.** MCP-server secrets live in the [Secrets Manager](07-secrets-manager.md); AuthN secures *access to Nexus*, not Nexus's access to backends. - **Transport security.** TLS termination is the [Gateway](04-gateway.md) / [Security](19-security.md) baseline. - **Plugin transport.** Out-of-process auth-backend plugins are wired via the [Plugin System](11-plugin-system.md) in Phase 5; this module defines only the in-process interface. ## Interfaces ```go // A resolved, verified identity. Attached to every request context after AuthN. type Principal struct { ID string // stable identity key, e.g. "user:alice", "agent:claude-01" Kind PrincipalKind // KindUser | KindAgent | KindService Provider string // "apikey", "oidc", "jwt", "ldap", "saml" Claims map[string]string // sub, email, groups, agent_id, ... Expires time.Time // zero for non-expiring credentials (e.g. API key) } type PrincipalKind string const ( KindUser PrincipalKind = "user" KindAgent PrincipalKind = "agent" KindService PrincipalKind = "service" ) // A pluggable authentication backend (tenet #3). type Provider interface { Name() string // "apikey", "oidc", ... // Authenticate inspects the request; ok=false if this provider does not // recognize the credential, err for a malformed/invalid one. Authenticate(ctx context.Context, r *http.Request) (Principal, bool, error) } // The authenticator runs providers in order and returns the first match. type Authenticator interface { Register(p Provider) error Resolve(ctx context.Context, r *http.Request) (Principal, error) // ErrUnauthenticated if none match } // API-key lifecycle. Keys are stored hashed; plaintext is returned once. type KeyManager interface { Issue(ctx context.Context, subject string, opts KeyOpts) (plaintext string, id string, err error) Verify(ctx context.Context, presented string) (Principal, error) Revoke(ctx context.Context, id string) error List(ctx context.Context, subject string) ([]KeyMeta, error) } ``` HTTP/API surface (behind the Gateway; TLS-terminated): - `POST /api/v1/auth/login` — interactive login → sets session cookie / returns JWT. - `POST /api/v1/auth/refresh` — exchange a refresh token for a fresh access token. - `POST /api/v1/auth/logout` — invalidate the current session. - `GET /api/v1/auth/oidc/callback` — OAuth2/OIDC authorization-code redirect target. - `GET /api/v1/auth/saml/acs` — SAML assertion consumer service (SSO, later). - `POST /api/v1/auth/keys` · `GET /api/v1/auth/keys` · `DELETE /api/v1/auth/keys/{id}` — API-key CRUD (RBAC-gated). MCP handshake: an agent presents its credential in the `initialize` request (bearer token or API key header on the Streamable HTTP transport); the resolved `Principal` is pinned to the MCP session for its lifetime. Request-resolution flow (data plane, per request): ``` 1. Gateway receives request (MCP over Streamable HTTP, or dashboard/API call). 2. Authenticator.Resolve iterates registered providers in priority order: apikey → jwt/session → oidc → (ldap/ad/saml later) 3. First provider that recognizes the credential returns (Principal, true). - none recognize it → ErrUnauthenticated → 401 (audited) - one recognizes but it's bad → error → 401 (audited, no fall-through) 4. Principal attached to request context (id, kind, provider, claims). 5. For agents: Principal.ID feeds AgentProfile.Identify (Module 14). 6. RBAC (Module 08) reads the Principal to scope the request. 7. Audit records the decision: {principal, provider, source_ip, outcome}. ``` ## Data - **Reads/writes** the **Identity** store (Architecture §8): `Users`, `AgentProfiles`, `Roles`, and **API keys** (stored as `{id, subject, hash, salt, created, last_used, expires, revoked}`). - **Reads** OIDC/SAML provider config (issuer URL, client id, JWKS endpoint, metadata) from the **Config** store; the OAuth **client secret** is a ref into the [Secrets Manager](07-secrets-manager.md), never inlined. - **Writes** authentication events to the **Audit** store (append-only). - JWT signing keys live in the **Secrets** store; the JWKS for verifying externally issued tokens is fetched and cached from the IdP. ## Dependencies - Sits behind the [Gateway](04-gateway.md), which invokes `Resolve` as the first middleware on the data-plane hot path. - Feeds the `Principal` to [RBAC](08-rbac.md) for authorization and to [AI Agent Profiles](14-agent-profiles.md) for identity→profile binding. - OAuth client secrets and JWT signing keys come from [Secrets](07-secrets-manager.md). - Every decision is recorded per [Security](19-security.md) §9 audit rules. - Backend providers are plugin points via the [Plugin System](11-plugin-system.md) (Phase 5). ## Failure modes & handling | Failure | Behavior | |---|---| | No provider recognizes the credential | `ErrUnauthenticated` → `401`; audited as a failed attempt with source IP. | | Malformed/expired JWT | `401`; client directed to `/auth/refresh` or re-login; never falls through to another provider as valid. | | IdP (OIDC/SAML) unreachable | Interactive login fails closed; already-issued, still-valid JWTs continue to work until expiry (no data-plane outage). | | JWKS fetch fails | Serve from cached keys; refuse tokens signed by unknown `kid` rather than trusting blindly. | | API key leaked/compromised | Immediate `Revoke`; hash comparison is constant-time; keys are rate-limited and can carry an expiry. | | Brute-force credential guessing | Per-source and per-subject rate limiting (Security §9) + exponential backoff; repeated failures alert via [Notifications](13-notifications.md). | | Clock skew on token validation | Bounded leeway (e.g. ±60s) on `nbf`/`exp`; beyond that, reject. | ## Security notes Honors Architecture §9. Credentials are never logged and never written into `DiscoveredResource`, config, or audit payloads — only credential **ids** and outcomes are recorded. API keys are stored as salted hashes (argon2id/bcrypt), compared in constant time, and shown in plaintext once. JWTs are short-lived, signed with a key from [Secrets](07-secrets-manager.md), and validated for signature, `iss`, `aud`, and expiry on every request. OAuth flows use authorization-code + PKCE; client secrets are secret-refs. All auth traffic is TLS-only (fail closed if terminated plaintext). Sessions bind to the `Principal` for their lifetime so a mid-session privilege change forces re-resolution. Fail closed everywhere: an unresolvable request is unauthenticated, never anonymous-with-access. ## Open questions - Should agent credentials be first-class API keys scoped to an [AgentProfile](14-agent-profiles.md), or a distinct credential type with its own lifecycle? - mTLS as an additional agent authentication provider for zero-shared-secret deployments? - Session store: stateless JWT only, or a server-side session table to enable instant global revocation (at a hot-path lookup cost)? - How to federate group/role claims from multiple IdPs into one [RBAC](08-rbac.md) role space without collisions? - Do we support step-up auth (re-authentication) for high-risk control-plane mutations? ## Milestone Delivered in **Phase 3** (Secure — data-plane hardening). Thin slice first: **API-key authentication** end to end — issue a key, present it on the MCP handshake and the control API, resolve a `Principal`, and audit the decision — so two differently keyed agents can be told apart. OIDC/OAuth + JWT sessions follow within the phase; LDAP/AD/SAML SSO are deferred (Roadmap "not early"). Contributes to the Phase 3 exit proof: two agents with different identities connect and are distinguished before RBAC scopes them.