Skip to content

Commit aa887c8

Browse files
committed
add error_description
1 parent 4fffe49 commit aa887c8

File tree

15 files changed

+56
-14
lines changed

15 files changed

+56
-14
lines changed

exercises/03.auth-info/02.problem.error/src/auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export function handleUnauthorized(request: Request) {
5252
headers: {
5353
// 🐨 if the request has an Authorization header, add an error auth param
5454
// 💰 `error="invalid_token"`
55+
// 🐨 also, if we have an Authorization header, add an error_description auth param
56+
// explaining that the token is invalid or expired
5557
'WWW-Authenticate': `Bearer realm="EpicMe", resource_metadata=${url.toString()}`,
5658
},
5759
})

exercises/03.auth-info/02.solution.error/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ export function handleUnauthorized(request: Request) {
5353
'WWW-Authenticate': [
5454
`Bearer realm="EpicMe"`,
5555
hasAuthHeader ? `error="invalid_token"` : null,
56+
hasAuthHeader
57+
? `error_description="The access token is invalid or expired"`
58+
: null,
5659
`resource_metadata=${url.toString()}`,
5760
]
5861
.filter(Boolean)

exercises/03.auth-info/03.problem.active/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ export function handleUnauthorized(request: Request) {
5656
'WWW-Authenticate': [
5757
`Bearer realm="EpicMe"`,
5858
hasAuthHeader ? `error="invalid_token"` : null,
59+
hasAuthHeader
60+
? `error_description="The access token is invalid or expired"`
61+
: null,
5962
`resource_metadata=${url.toString()}`,
6063
]
6164
.filter(Boolean)

exercises/03.auth-info/03.solution.active/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export function handleUnauthorized(request: Request) {
6060
'WWW-Authenticate': [
6161
`Bearer realm="EpicMe"`,
6262
hasAuthHeader ? `error="invalid_token"` : null,
63+
hasAuthHeader
64+
? `error_description="The access token is invalid or expired"`
65+
: null,
6366
`resource_metadata=${url.toString()}`,
6467
]
6568
.filter(Boolean)

exercises/03.auth-info/README.mdx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Auth Info
22

3-
Authentication is the backbone of secure applications. In this exercise, you'll learn how to introspect tokens, handle invalid tokens, and determine if a token is active—all essential for building robust, user-friendly authentication flows in MCP servers.
3+
So the client sends your resource server a token. Sometimes this is enough, but often you need to be able to know who that token represents and the scopes associated to that token. This is often referred to as "introspection".
44

5-
Why does this matter? Without proper token introspection and error handling, your app can't reliably know who the user is or what they're allowed to do. This can lead to security holes or a poor user experience.
5+
In this exercise, you'll learn how to introspect tokens, handle invalid tokens, and determine if a token is active—all essential for building robust, user-friendly authentication flows in MCP servers.
66

77
## What you'll learn
88

@@ -19,9 +19,13 @@ Why does this matter? Without proper token introspection and error handling, you
1919
### Example: Introspecting a Token
2020

2121
```ts
22-
const result = await client.auth.introspect({
23-
token: 'eyJhbGciOi...',
22+
const validateUrl = new URL('/oauth/introspection', 'https://auth.example.com')
23+
const resp = await fetch(validateUrl, {
24+
method: 'POST',
25+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
26+
body: new URLSearchParams({ token }),
2427
})
28+
const result = await resp.json()
2529
if (!result.active) {
2630
throw new Error('Token is not active!')
2731
}
@@ -53,24 +57,21 @@ If a token is invalid, your server should return a clear error response. This he
5357

5458
```mermaid
5559
sequenceDiagram
56-
participant Client
57-
participant Server
58-
participant DB
59-
Client->>Server: /auth/introspect (token)
60-
Server->>DB: Validate token
60+
participant Resource Server
61+
participant Auth Server
62+
Resource Server->>Auth Server: /auth/introspect (token)
63+
Auth Server->>Auth Server: Validate token
6164
alt Token valid
62-
DB-->>Server: Token info (active)
63-
Server-->>Client: Token details (active: true)
65+
Auth Server-->>Resource Server: Token details (active: true)
6466
else Token invalid
65-
DB-->>Server: Not found/expired
66-
Server-->>Client: Error (active: false)
67+
Auth Server-->>Resource Server: Error (active: false)
6768
end
6869
```
6970

7071
## Recommended Practices
7172

7273
- Always validate tokens before trusting any claims.
73-
- Handle invalid tokens gracefully—never crash or expose stack traces.
74+
- Handle invalid tokens gracefully
7475
- Use introspection to power user-specific features and permissions.
7576

7677
- 📜 [OAuth 2.0 Token Introspection Spec](https://datatracker.ietf.org/doc/html/rfc7662)

exercises/04.user/01.problem.token/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export function handleUnauthorized(request: Request) {
6060
'WWW-Authenticate': [
6161
`Bearer realm="EpicMe"`,
6262
hasAuthHeader ? `error="invalid_token"` : null,
63+
hasAuthHeader
64+
? `error_description="The access token is invalid or expired"`
65+
: null,
6366
`resource_metadata=${url.toString()}`,
6467
]
6568
.filter(Boolean)

exercises/04.user/01.solution.token/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export function handleUnauthorized(request: Request) {
6060
'WWW-Authenticate': [
6161
`Bearer realm="EpicMe"`,
6262
hasAuthHeader ? `error="invalid_token"` : null,
63+
hasAuthHeader
64+
? `error_description="The access token is invalid or expired"`
65+
: null,
6366
`resource_metadata=${url.toString()}`,
6467
]
6568
.filter(Boolean)

exercises/04.user/02.problem.user/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export function handleUnauthorized(request: Request) {
6060
'WWW-Authenticate': [
6161
`Bearer realm="EpicMe"`,
6262
hasAuthHeader ? `error="invalid_token"` : null,
63+
hasAuthHeader
64+
? `error_description="The access token is invalid or expired"`
65+
: null,
6366
`resource_metadata=${url.toString()}`,
6467
]
6568
.filter(Boolean)

exercises/04.user/02.solution.user/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export function handleUnauthorized(request: Request) {
6060
'WWW-Authenticate': [
6161
`Bearer realm="EpicMe"`,
6262
hasAuthHeader ? `error="invalid_token"` : null,
63+
hasAuthHeader
64+
? `error_description="The access token is invalid or expired"`
65+
: null,
6366
`resource_metadata=${url.toString()}`,
6467
]
6568
.filter(Boolean)

exercises/05.scopes/01.problem.check-scopes/src/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export function handleUnauthorized(request: Request) {
6868
'WWW-Authenticate': [
6969
`Bearer realm="EpicMe"`,
7070
hasAuthHeader ? `error="invalid_token"` : null,
71+
hasAuthHeader
72+
? `error_description="The access token is invalid or expired"`
73+
: null,
7174
`resource_metadata=${url.toString()}`,
7275
]
7376
.filter(Boolean)

0 commit comments

Comments
 (0)