|
1 | 1 | # Introspect |
| 2 | + |
| 3 | +👨💼 Ok, so we've got the user's auth token, but what do we do with it? Our server needs to know: |
| 4 | + |
| 5 | +- Is the token valid? |
| 6 | +- Is it still active? |
| 7 | +- Who is this person? (user ID) |
| 8 | +- What permissions do they have? (scopes) |
| 9 | + |
| 10 | +So we need to "introspect" the token to get this information. |
| 11 | + |
| 12 | +```ts |
| 13 | +// When a user makes a request, we need to resolve their token |
| 14 | +const authInfo = await resolveAuthInfo(request.headers.get('authorization')) |
| 15 | +if (!authInfo) { |
| 16 | + return handleUnauthorized(request) |
| 17 | +} |
| 18 | + |
| 19 | +console.log(`User ${authInfo.extra.userId} from ${authInfo.clientId}`) |
| 20 | +console.log(`Has scopes: ${authInfo.scopes.join(', ')}`) |
| 21 | +``` |
| 22 | + |
| 23 | +Auth providers implement this slightly differently, but the core concept is the same: you send the token to the auth server, and it tells you everything you need to know about that token. Some auth providers store their token as a JWT which means the information is contained within the token itself and you don't need to perform a request to the auth server to get the information. |
| 24 | + |
| 25 | +One way or another though, you need to determine whether the token is valid and active. |
| 26 | + |
| 27 | +<callout-info> |
| 28 | + 🔍 Token validation is essential for user-specific features. Without it, you |
| 29 | + can't personalize the experience or enforce proper permissions. |
| 30 | +</callout-info> |
| 31 | + |
| 32 | +The introspection process involves making a POST request to the auth server's introspection endpoint with the token. The response contains the user ID (`sub`), client ID (`client_id`), and scopes (`scope`) that tell you exactly what this token represents. |
| 33 | + |
| 34 | +```ts |
| 35 | +// Example introspection request |
| 36 | +const validateUrl = new URL('/oauth/introspection', 'https://auth.example.com') |
| 37 | +const resp = await fetch(validateUrl, { |
| 38 | + method: 'POST', |
| 39 | + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, |
| 40 | + body: new URLSearchParams({ token }), |
| 41 | +}) |
| 42 | + |
| 43 | +const result = await resp.json() |
| 44 | +// result.sub = user ID |
| 45 | +// result.client_id = app the user is using |
| 46 | +// result.scope = space-separated list of permissions |
| 47 | +``` |
| 48 | + |
| 49 | +<callout-info> |
| 50 | + 🎯 When you're finished with this exercise, the behavior won't change |
| 51 | + visually. Consider adding a `console.log` to see the introspection results in |
| 52 | + action! |
| 53 | +</callout-info> |
| 54 | + |
| 55 | +```mermaid |
| 56 | +sequenceDiagram |
| 57 | + MCP Client->>MCP Server: POST /mcp (Authorization: Bearer token) |
| 58 | + MCP Server->>Auth Server: POST /oauth/introspection (token) |
| 59 | + alt Token is active |
| 60 | + Auth Server-->>MCP Server: Returns user info (sub, client_id, scope) |
| 61 | + MCP Server-->>MCP Client: Processes request with user context |
| 62 | + else Token is invalid/expired |
| 63 | + Auth Server-->>MCP Server: Returns error or inactive status |
| 64 | + MCP Server-->>MCP Client: Returns unauthorized response |
| 65 | + end |
| 66 | +``` |
| 67 | + |
| 68 | +<callout-muted> |
| 69 | + 📜 For more details on token introspection, see the [OAuth 2.0 Token |
| 70 | + Introspection RFC](https://datatracker.ietf.org/doc/html/rfc7662). |
| 71 | +</callout-muted> |
| 72 | + |
| 73 | +The goal is to transform anonymous tokens into rich user context, enabling personalized experiences and proper permission enforcement. |
| 74 | + |
| 75 | +Now, let's implement the `resolveAuthInfo` function to make this happen! |
0 commit comments