|
| 1 | +--- |
| 2 | +myst: |
| 3 | + html_meta: |
| 4 | + "description lang=en": | |
| 5 | + How RedisVL MCP authenticates clients with JWT bearer tokens and gates |
| 6 | + read vs write access by scope or role claim. |
| 7 | +--- |
| 8 | + |
| 9 | +# Authenticate RedisVL MCP |
| 10 | + |
| 11 | +This guide explains how the RedisVL MCP server authenticates clients on its HTTP |
| 12 | +transports and how it gates read vs write access. It also draws the boundary |
| 13 | +between what RedisVL enforces and what belongs in a gateway or policy layer. |
| 14 | + |
| 15 | +```{note} |
| 16 | +Authentication applies only to the HTTP transports (`streamable-http`, `sse`). |
| 17 | +The `stdio` transport is a local subprocess with no network surface and is never |
| 18 | +authenticated. |
| 19 | +``` |
| 20 | + |
| 21 | +## What RedisVL Enforces |
| 22 | + |
| 23 | +RedisVL validates a bearer **JWT** that an existing identity provider (IdP) |
| 24 | +issued. It does not run an OAuth authorization server and does not issue tokens. |
| 25 | + |
| 26 | +On each request it checks: |
| 27 | + |
| 28 | +- **Signature**, against a JWKS endpoint or a static public key. |
| 29 | +- **Issuer** (`iss`), so only tokens from your IdP are accepted. |
| 30 | +- **Audience** (`aud`), so a token minted for a different service cannot be |
| 31 | + replayed against this server (RFC 8707). |
| 32 | +- **Expiration**: a present `exp` in the past is rejected, and tokens are |
| 33 | + required to carry `exp` and `iat` (configurable via `required_claims`), so a |
| 34 | + token with no expiration, which would never expire, is rejected. |
| 35 | +- **Required scopes** to connect, and (optionally) a **read scope** to call |
| 36 | + `search-records` and a **write scope** to call `upsert-records`. |
| 37 | + |
| 38 | +```{important} |
| 39 | +This is **coarse** authorization: it decides whether a caller may connect and |
| 40 | +whether it may read or write. It does **not** map token claims to a Redis ACL |
| 41 | +user, a per-tenant index, or query filters. See [The Authorization Boundary](#the-authorization-boundary). |
| 42 | +``` |
| 43 | + |
| 44 | +## OAuth: Which Part RedisVL Handles |
| 45 | + |
| 46 | +In OAuth terms, RedisVL MCP is a **resource server**: it validates access |
| 47 | +tokens that your identity provider issued. It does not run an OAuth login flow |
| 48 | +and does not issue tokens. |
| 49 | + |
| 50 | +```mermaid |
| 51 | +flowchart LR |
| 52 | + Client -->|1 - OAuth login flow| IdP[Identity Provider] |
| 53 | + IdP -->|2 - issues JWT access token| Client |
| 54 | + Client -->|3 - Bearer JWT| MCP[RedisVL MCP] |
| 55 | + MCP -->|validates token| Redis[(Redis)] |
| 56 | +``` |
| 57 | + |
| 58 | +Steps 1 and 2 (obtaining the token) are handled by your client and IdP. RedisVL |
| 59 | +only performs the validation in step 3. As a result: |
| 60 | + |
| 61 | +- It works with any OAuth 2.0 / OIDC provider (for example Auth0, Okta, Azure AD |
| 62 | + / Entra, Cognito, Keycloak): point `jwks_uri` at the provider and validate its |
| 63 | + tokens. |
| 64 | +- RedisVL does not broker interactive "log in with..." flows and does not mint |
| 65 | + tokens. |
| 66 | + |
| 67 | +You would only need more than token validation when you want the server itself |
| 68 | +to drive an interactive browser login (an OAuth proxy), or to act as its own |
| 69 | +authorization server that issues tokens. Both are out of scope today. If |
| 70 | +interactive login is ever needed, a single generic `oauth-proxy` option can be |
| 71 | +added behind the `auth.type` switch. For enterprise and agent deployments where |
| 72 | +the caller already holds a token, JWT validation is sufficient. |
| 73 | + |
| 74 | +## Request Flow |
| 75 | + |
| 76 | +```mermaid |
| 77 | +sequenceDiagram |
| 78 | + actor User |
| 79 | + participant IdP as Identity Provider |
| 80 | + participant MCP as RedisVL MCP Server |
| 81 | + participant Redis |
| 82 | +
|
| 83 | + User->>IdP: Authenticate |
| 84 | + IdP-->>User: Signed JWT (iss, aud, scopes/roles) |
| 85 | + User->>MCP: MCP request + Bearer JWT |
| 86 | + MCP->>MCP: Validate signature (JWKS / public key) |
| 87 | + MCP->>MCP: Check issuer + audience |
| 88 | + alt token invalid / wrong audience / missing connect scope |
| 89 | + MCP-->>User: 401 Unauthorized |
| 90 | + else token valid |
| 91 | + MCP->>MCP: Gate tool by read / write scope |
| 92 | + alt scope present |
| 93 | + MCP->>Redis: Search or upsert (single configured ACL user) |
| 94 | + Redis-->>MCP: Results |
| 95 | + MCP-->>User: Tool result |
| 96 | + else scope missing |
| 97 | + MCP-->>User: Forbidden |
| 98 | + end |
| 99 | + end |
| 100 | +``` |
| 101 | + |
| 102 | +## Configure JWT Authentication |
| 103 | + |
| 104 | +Add a `server.auth` block to your MCP config. Secrets can be injected with |
| 105 | +`${ENV}` substitution. |
| 106 | + |
| 107 | +```yaml |
| 108 | +server: |
| 109 | + redis_url: ${REDIS_URL:-redis://localhost:6379} |
| 110 | + auth: |
| 111 | + type: jwt |
| 112 | + jwks_uri: ${MCP_JWKS_URI} # or set public_key for a static key |
| 113 | + issuer: ${MCP_ISSUER} |
| 114 | + audience: api://redisvl-mcp |
| 115 | + required_scopes: [kb.read] # required to connect |
| 116 | + required_claims: [exp, iat] # claims every token must carry (default) |
| 117 | + read_scope: kb.search.read # required for search-records |
| 118 | + write_scope: kb.search.write # required for upsert-records |
| 119 | + |
| 120 | +indexes: |
| 121 | + knowledge: |
| 122 | + redis_name: docs_index |
| 123 | + search: |
| 124 | + type: fulltext |
| 125 | + runtime: |
| 126 | + text_field_name: content |
| 127 | +``` |
| 128 | +
|
| 129 | +Every field is also settable through `REDISVL_MCP_AUTH_*` environment variables, |
| 130 | +which take precedence over the YAML block. |
| 131 | + |
| 132 | +## Choosing the Authorization Claim |
| 133 | + |
| 134 | +Different identity providers carry authorization in different claims. The |
| 135 | +default JWT scope claim is `scp` (or `scope`). Some enterprise providers carry |
| 136 | +authorization in a **`roles`** claim instead, which does not appear in the |
| 137 | +standard scope set. |
| 138 | + |
| 139 | +```mermaid |
| 140 | +flowchart TD |
| 141 | + A[Validated JWT claims] --> B{authorization_claim} |
| 142 | + B -->|scp / scope| C["access.scopes<br/>e.g. kb.read"] |
| 143 | + B -->|roles| D["access.claims.roles<br/>e.g. kb.search.read"] |
| 144 | + C --> E[Check read_scope / write_scope] |
| 145 | + D --> E |
| 146 | + E -->|present| F[Allow tool] |
| 147 | + E -->|absent| G[Deny tool] |
| 148 | +``` |
| 149 | + |
| 150 | +Set the claim that holds your authorization values so read and write gating |
| 151 | +reads the right place: |
| 152 | + |
| 153 | +```yaml |
| 154 | +server: |
| 155 | + auth: |
| 156 | + type: jwt |
| 157 | + # ... |
| 158 | + authorization_claim: roles # default: scp |
| 159 | + read_scope: kb.search.read |
| 160 | + write_scope: kb.search.write |
| 161 | +``` |
| 162 | + |
| 163 | +A token like the following would then pass the read gate, because |
| 164 | +`kb.search.read` is present in `roles`: |
| 165 | + |
| 166 | +```json |
| 167 | +{ |
| 168 | + "iss": "https://your-idp.example/{tenant}/v2.0", |
| 169 | + "aud": "api://redisvl-mcp", |
| 170 | + "sub": "nitin", |
| 171 | + "roles": ["kb.search.read"], |
| 172 | + "scp": "kb.read" |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +## The Authorization Boundary |
| 177 | + |
| 178 | +RedisVL MCP authenticates the caller and gates read vs write. It does **not** |
| 179 | +translate token claims (such as a tenant id or role) into a specific Redis ACL |
| 180 | +user, a per-tenant index, or injected query filters. The server holds one Redis |
| 181 | +connection for one index, established at startup. |
| 182 | + |
| 183 | +Fine-grained, per-tenant data isolation belongs in a **gateway or policy layer** |
| 184 | +in front of the MCP server, which validates the token, looks up a binding of |
| 185 | +claim to Redis identity, and injects credentials and filters. |
| 186 | + |
| 187 | +```mermaid |
| 188 | +flowchart LR |
| 189 | + subgraph Gateway["Gateway / policy layer (out of scope for RedisVL)"] |
| 190 | + T[Validate token] --> M["Map claims to<br/>Redis user + index + filters"] |
| 191 | + end |
| 192 | + subgraph RedisVL["RedisVL MCP (this guide)"] |
| 193 | + A[Validate JWT] --> S[Gate read / write by scope] |
| 194 | + end |
| 195 | + Client --> Gateway --> RedisVL --> Redis[(Redis)] |
| 196 | +``` |
| 197 | + |
| 198 | +Use RedisVL's JWT validation for authentication and coarse read/write |
| 199 | +authorization. Layer a gateway on top when you need per-tenant Redis ACL |
| 200 | +enforcement. |
| 201 | + |
| 202 | +## See Also |
| 203 | + |
| 204 | +- {doc}`mcp`: run and configure the RedisVL MCP server. |
| 205 | + |
0 commit comments