-
Notifications
You must be signed in to change notification settings - Fork 336
Description
Authentication & Authorization - SSO + Identity-Provider Integration
🧭 Epic
Title: Add SSO & IdP-Issued Tokens to Gateway
Goal: Replace hard-coded credentials with full SSO login (SAML/OIDC) for the web UI and bearer-token introspection for the API, so every request carries a verified user identity & scopes.
Why now: We run several gateways scoped to different teams; central auth will slash onboarding time and guarantee users only hit tools & servers they're entitled to.
Standards:
- https://modelcontextprotocol.io/specification/draft/basic/authorization#authorization-flow-steps
- https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-server-discovery
- https://datatracker.ietf.org/doc/html/rfc9728
🧭 Type of Feature
- Security hardening
- New functionality (experimental)
🙋♂️ User Story 1 - Browser SSO Flow
As a: Platform engineer
I want: the UI to redirect unauthenticated users to our corporate IdP and honor SAML/OIDC assertions
So that: users log in once and land back on the dashboard already mapped to their roles.
✅ Acceptance Criteria
Scenario: Successful SSO login
Given an unauthenticated browser visits "/"
When the gateway redirects to the IdP login page
And the user completes the IdP challenge
Then the gateway MUST create an encrypted session cookie
And MUST attach the user's uid & groups to subsequent requests
🙋♂️ User Story 2 - API Token Introspection
As a: Tool developer
I want: the REST/MCP API to accept only IdP-issued JWTs or opaque reference tokens
So that: every call is cryptographically bound to a user & scopes (least privilege).
✅ Acceptance Criteria
Scenario: Reject expired or tampered tokens
Given a client sends "Authorization: Bearer <token>"
When the token fails signature or expiry checks
Then respond 401 "invalid_token"
And no business logic executes
🙋♂️ User Story 3 - Role-Based Access Control (RBAC)
As a: Security admin
I want: gateway routes & tool invocations guarded by group/role policies
So that: "finance-analyst" users cannot hit "devops.*" tools.
✅ Acceptance Criteria
Scenario: Enforce RBAC on tool call
Given user belongs to groups ["finance-analyst"]
And attempts to invoke "devops.kubectl"
Then respond 403 "forbidden_scope"
📐 Design Sketch
flowchart TD
Browser -->|SAML/OIDC| IdP[(Corporate IdP)]
IdP --> GW[Gateway Auth Service]
subgraph Gateway
GW --> AC[Access Checker]
AC --> API["UI / JSON-RPC / REST"]
end
Component | Change | Detail |
---|---|---|
auth_service.py |
NEW | SAML/OIDC callback handling, token validator |
Middleware | UPDATE | Authorization: bearer token extraction & cache |
DB | NEW | users , groups , sessions tables |
Config | ADD | OIDC_* / SAML_* settings |
sequenceDiagram
participant B as Browser/User-Agent
participant C as MCP Client
participant AS as Authorization Server
participant RS as MCP Resource Server
Note over C,RS: 🔍 Phase 1: Discovery & Initial Request
C->>RS: MCP request without token
RS->>C: HTTP 401 Unauthorized\nWWW-Authenticate: Bearer realm="MCP",\nresource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
Note over C,AS: 🔍 Phase 2: Authorization Server Discovery
C->>RS: GET /.well-known/oauth-protected-resource
RS->>C: Protected Resource Metadata\n{authorization_servers: ["https://auth.example.com"]}
C->>AS: GET /.well-known/oauth-authorization-server
AS->>C: Authorization Server Metadata\n{authorization_endpoint, token_endpoint,\nregistration_endpoint, pkce_required: true}
Note over C,AS: 🔧 Phase 3: Dynamic Client Registration (RFC 7591)
alt Dynamic Client Registration Supported
C->>AS: POST /register\n{client_name: "MCP Client",\ngrant_types: ["authorization_code"],\nresponse_types: ["code"],\nredirect_uris: ["http://localhost:8080/callback"]}
AS->>C: Client Credentials\n{client_id: "abc123", client_secret: "xyz789"}
else Pre-registered Client
Note over C: Use hardcoded client_id
end
Note over C,B: 🔐 Phase 4: Authorization Request with PKCE & Resource Parameter
C->>C: Generate PKCE parameters\ncode_verifier = random_string(43-128)\ncode_challenge = SHA256(code_verifier)\ncode_challenge_method = "S256"
C->>B: Redirect to Authorization URL\nhttps://auth.example.com/authorize?\nresponse_type=code&\nclient_id=abc123&\nredirect_uri=http://localhost:8080/callback&\nscope=mcp:read mcp:write&\nstate=random_state&\ncode_challenge=xyz&\ncode_challenge_method=S256&\nresource=https://mcp.example.com
Note over B,AS: 🔐 Phase 5: User Authorization
B->>AS: Authorization request with all parameters
AS->>AS: Validate client_id, redirect_uri,\nresource parameter, PKCE challenge
AS->>B: Show consent screen\n"MCP Client wants to access\nhttps://mcp.example.com on your behalf"
B->>AS: User authorizes access
Note over AS,C: 🎫 Phase 6: Authorization Code Exchange
AS->>B: Redirect to callback\nhttp://localhost:8080/callback?\ncode=auth_code_123&state=random_state
B->>C: Authorization code callback
C->>C: Verify state parameter matches
C->>AS: POST /token\n{grant_type: "authorization_code",\ncode: "auth_code_123",\nredirect_uri: "http://localhost:8080/callback",\nclient_id: "abc123",\nclient_secret: "xyz789",\ncode_verifier: "original_verifier",\nresource: "https://mcp.example.com"}
AS->>AS: Validate:\n• Authorization code\n• PKCE code_verifier\n• Resource parameter\n• Client credentials
AS->>C: Access Token Response\n{access_token: "eyJ...",\ntoken_type: "Bearer",\nexpires_in: 3600,\nrefresh_token: "refresh_123",\nscope: "mcp:read mcp:write",\naud: "https://mcp.example.com"}
Note over C,RS: 🚀 Phase 7: Authenticated MCP Communication
C->>RS: MCP request\nAuthorization: Bearer eyJ...\nContent-Type: application/json\n{jsonrpc: "2.0", method: "list_tools"}
RS->>RS: Validate token:\n• Signature verification\n• Expiry check\n• Audience validation (aud: mcp.example.com)\n• Scope verification
RS->>C: MCP response\n{jsonrpc: "2.0", result: {tools: [...]}}
Note over C,RS: 🔄 Phase 8: Token Refresh (when needed)
alt Token Expired
C->>AS: POST /token\n{grant_type: "refresh_token",\nrefresh_token: "refresh_123",\nclient_id: "abc123",\nclient_secret: "xyz789",\nresource: "https://mcp.example.com"}
AS->>C: New Access Token\n{access_token: "new_eyJ...",\nrefresh_token: "new_refresh_456"}
end
🔄 Roll-out Plan
- Phase 0: Feature-flag
EXPERIMENTAL_SSO
off by default. - Phase 1: Shadow-mode (accept either legacy creds or SSO) in staging.
- Phase 2: Enforce SSO only; auto-migrate active sessions.
- Phase 3: Remove legacy credential endpoints & rotate secrets.
📝 Spec-Draft Clauses
- Auth Clause - "Gateways MUST authenticate requests via IdP tokens or mutual TLS."
- RBAC Clause - "Servers SHOULD map IdP ‘groups' claims to tool/route policies."
📣 Next Steps
- Spike OIDC library integration with GitHub sandbox.
- Draft database schema migration.
- Open follow-up PR to wire RBAC middleware.