@agent-id/nextjs 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -21
- package/dist/index.d.mts +33 -33
- package/dist/index.d.ts +33 -33
- package/dist/index.js +27 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @agent-id/nextjs
|
|
2
2
|
|
|
3
|
-
Next.js
|
|
3
|
+
Next.js proxy that automatically detects AI-agent traffic and requires a valid [AgentID](https://agentpass.vercel.app) JWT. Human browser traffic always passes through untouched.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -8,22 +8,22 @@ Next.js middleware that automatically detects AI-agent traffic and requires a va
|
|
|
8
8
|
npm install @agent-id/nextjs
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Requires `next >=
|
|
11
|
+
Requires `next >= 16`.
|
|
12
12
|
|
|
13
13
|
## Setup — 2 steps
|
|
14
14
|
|
|
15
|
-
### 1. Add the
|
|
15
|
+
### 1. Add the proxy
|
|
16
16
|
|
|
17
|
-
Create
|
|
17
|
+
Create `proxy.ts` in your project root (same level as `app/`):
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
import {
|
|
20
|
+
import { createAgentIDMiddleware } from '@agent-id/nextjs';
|
|
21
21
|
import type { NextRequest } from 'next/server';
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const agentID = createAgentIDMiddleware();
|
|
24
24
|
|
|
25
|
-
export function
|
|
26
|
-
return
|
|
25
|
+
export function proxy(request: NextRequest) {
|
|
26
|
+
return agentID(request);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Protect your API routes
|
|
@@ -32,20 +32,20 @@ export const config = {
|
|
|
32
32
|
};
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
That's it. The
|
|
35
|
+
That's it. The proxy now:
|
|
36
36
|
- Lets all human browser traffic through unchanged
|
|
37
|
-
- Requires a valid
|
|
37
|
+
- Requires a valid AgentID JWT from any AI agent / bot
|
|
38
38
|
- Returns `403 AGENT_UNAUTHORIZED` when the JWT is missing or invalid
|
|
39
39
|
|
|
40
40
|
### 2. Read the verified identity in your route handlers (optional)
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
// app/api/anything/route.ts
|
|
44
|
-
import {
|
|
44
|
+
import { getAgentIDResult } from '@agent-id/nextjs';
|
|
45
45
|
import type { NextRequest } from 'next/server';
|
|
46
46
|
|
|
47
47
|
export async function GET(request: NextRequest) {
|
|
48
|
-
const agent =
|
|
48
|
+
const agent = getAgentIDResult(request);
|
|
49
49
|
|
|
50
50
|
if (agent.verified) {
|
|
51
51
|
// Verified AI agent — claims are fully typed
|
|
@@ -63,41 +63,41 @@ export async function GET(request: NextRequest) {
|
|
|
63
63
|
Agents add one header to every request:
|
|
64
64
|
|
|
65
65
|
```
|
|
66
|
-
Authorization: Bearer <
|
|
66
|
+
Authorization: Bearer <agentid-jwt>
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
The JWT is obtained by completing a BankID flow at [agentpass.vercel.app](https://agentpass.vercel.app). Tokens are valid for 1 hour.
|
|
70
70
|
|
|
71
71
|
## Options
|
|
72
72
|
|
|
73
|
-
All options are optional — `
|
|
73
|
+
All options are optional — `createAgentIDMiddleware()` with no arguments works out of the box.
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
|
-
|
|
76
|
+
createAgentIDMiddleware({
|
|
77
77
|
// Return 403 when an agent has no valid token (default: true).
|
|
78
78
|
// Set to false to let unverified agents through (useful for logging / gradual rollout).
|
|
79
79
|
blockUnauthorizedAgents: true,
|
|
80
80
|
|
|
81
|
-
// Override the JWKS endpoint — only needed if you self-host
|
|
82
|
-
jwksUrl: 'https://your-
|
|
81
|
+
// Override the JWKS endpoint — only needed if you self-host AgentID.
|
|
82
|
+
jwksUrl: 'https://your-agentid.example.com/api/jwks',
|
|
83
83
|
|
|
84
84
|
// Clock skew tolerance in seconds (default: 30).
|
|
85
85
|
clockTolerance: 30,
|
|
86
86
|
|
|
87
87
|
// Fully custom response when an agent is rejected.
|
|
88
88
|
onUnauthorizedAgent: (request, reason) =>
|
|
89
|
-
NextResponse.json({ error: 'No
|
|
89
|
+
NextResponse.json({ error: 'No AgentID token', reason }, { status: 403 }),
|
|
90
90
|
})
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
## What gets verified
|
|
94
94
|
|
|
95
|
-
Verification is **fully offline** after the first request. The public key is fetched once from the
|
|
95
|
+
Verification is **fully offline** after the first request. The public key is fetched once from the AgentID JWKS endpoint and cached for 1 hour — no per-request network call.
|
|
96
96
|
|
|
97
97
|
| Check | Requirement |
|
|
98
98
|
|---|---|
|
|
99
99
|
| Signature | RS256 — `alg:none` and HS256 are explicitly rejected |
|
|
100
|
-
| Issuer (`iss`) | Must equal `"
|
|
100
|
+
| Issuer (`iss`) | Must equal `"agentid"` |
|
|
101
101
|
| Expiry (`exp`) | Must be in the future |
|
|
102
102
|
| `auth_method` | Must equal `"bankid"` |
|
|
103
103
|
|
|
@@ -107,7 +107,7 @@ Verification is **fully offline** after the first request. The public key is fet
|
|
|
107
107
|
|---|---|
|
|
108
108
|
| `sub` | Pseudonymous stable user ID (HMAC-SHA256 of BankID personal number — non-reversible, same person always gets the same ID) |
|
|
109
109
|
| `auth_method` | Always `"bankid"` |
|
|
110
|
-
| `iss` | `"
|
|
110
|
+
| `iss` | `"agentid"` |
|
|
111
111
|
| `exp` | Unix timestamp — 1 hour from issue |
|
|
112
112
|
| `iat` | Unix timestamp — when issued |
|
|
113
113
|
| `jti` | Unique token ID |
|
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* JWT claims present in every
|
|
4
|
+
* JWT claims present in every AgentID token.
|
|
5
5
|
*
|
|
6
6
|
* The `sub` field is a pseudonymous identifier derived via HMAC-SHA256 of the
|
|
7
7
|
* user's BankID personal number. It is stable per person but non-reversible —
|
|
8
8
|
* organisations cannot extract the personal number from it.
|
|
9
9
|
*/
|
|
10
|
-
interface
|
|
10
|
+
interface AgentIDClaims {
|
|
11
11
|
/** Pseudonymous, stable per-user identifier (HMAC-SHA256 of personal number). */
|
|
12
12
|
sub: string;
|
|
13
|
-
/** Issuer — always "
|
|
13
|
+
/** Issuer — always "agentid". */
|
|
14
14
|
iss: string;
|
|
15
15
|
/** Issued-at Unix timestamp. */
|
|
16
16
|
iat: number;
|
|
@@ -21,27 +21,27 @@ interface AgentPassClaims {
|
|
|
21
21
|
/** Authentication method — always "bankid" in this version. */
|
|
22
22
|
auth_method: "bankid";
|
|
23
23
|
}
|
|
24
|
-
interface
|
|
24
|
+
interface AgentIDVerified {
|
|
25
25
|
verified: true;
|
|
26
|
-
claims:
|
|
26
|
+
claims: AgentIDClaims;
|
|
27
27
|
}
|
|
28
|
-
interface
|
|
28
|
+
interface AgentIDUnverified {
|
|
29
29
|
verified: false;
|
|
30
30
|
/** Why verification was skipped or failed. */
|
|
31
31
|
reason: "not_agent" | "no_token" | "invalid_token";
|
|
32
32
|
}
|
|
33
|
-
/** Result attached to every request processed by the
|
|
34
|
-
type
|
|
33
|
+
/** Result attached to every request processed by the AgentID middleware. */
|
|
34
|
+
type AgentIDResult = AgentIDVerified | AgentIDUnverified;
|
|
35
35
|
interface VerifierOptions {
|
|
36
36
|
/**
|
|
37
|
-
* URL of the
|
|
37
|
+
* URL of the AgentID JWKS endpoint.
|
|
38
38
|
* Must use HTTPS (except `localhost` / `127.0.0.1` in development).
|
|
39
39
|
*
|
|
40
40
|
* @default 'https://agentpass.vercel.app/api/jwks'
|
|
41
41
|
*/
|
|
42
42
|
jwksUrl?: string;
|
|
43
43
|
/**
|
|
44
|
-
* Block agent requests that carry no valid
|
|
44
|
+
* Block agent requests that carry no valid AgentID token.
|
|
45
45
|
* When `false` the middleware still runs but sets an unverified result
|
|
46
46
|
* on the request instead of returning 403.
|
|
47
47
|
*
|
|
@@ -57,7 +57,7 @@ interface VerifierOptions {
|
|
|
57
57
|
clockTolerance?: number;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
type
|
|
60
|
+
type AgentIDMiddlewareOptions = VerifierOptions & {
|
|
61
61
|
/**
|
|
62
62
|
* Optional callback invoked when an agent is detected but carries no
|
|
63
63
|
* valid token. Return a `NextResponse` to override the default 403.
|
|
@@ -65,47 +65,47 @@ type AgentPassMiddlewareOptions = VerifierOptions & {
|
|
|
65
65
|
onUnauthorizedAgent?: (request: NextRequest, reason: string) => NextResponse | void;
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
|
-
* Factory that returns a Next.js
|
|
69
|
-
* traffic and enforces
|
|
68
|
+
* Factory that returns a Next.js proxy function which detects AI-agent
|
|
69
|
+
* traffic and enforces AgentID JWT authentication.
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
72
|
* ```ts
|
|
73
|
-
* //
|
|
74
|
-
* import {
|
|
73
|
+
* // proxy.ts (project root)
|
|
74
|
+
* import { createAgentIDMiddleware } from '@agent-id/nextjs';
|
|
75
75
|
*
|
|
76
|
-
* const
|
|
76
|
+
* const agentID = createAgentIDMiddleware({
|
|
77
77
|
* blockUnauthorizedAgents: true,
|
|
78
78
|
* });
|
|
79
79
|
*
|
|
80
|
-
* export function
|
|
81
|
-
* return
|
|
80
|
+
* export function proxy(request: NextRequest) {
|
|
81
|
+
* return agentID(request);
|
|
82
82
|
* }
|
|
83
83
|
*
|
|
84
84
|
* export const config = { matcher: '/api/:path*' };
|
|
85
85
|
* ```
|
|
86
86
|
*/
|
|
87
|
-
declare function
|
|
87
|
+
declare function createAgentIDMiddleware(options?: AgentIDMiddlewareOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
88
88
|
|
|
89
89
|
interface VerifyTokenOptions {
|
|
90
90
|
jwksUrl?: string;
|
|
91
91
|
clockTolerance?: number;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
* Verify an
|
|
94
|
+
* Verify an AgentID JWT and return its decoded claims.
|
|
95
95
|
*
|
|
96
96
|
* Security guarantees
|
|
97
97
|
* ───────────────────
|
|
98
98
|
* • Signature — RS256, verified against the live JWKS public key.
|
|
99
99
|
* • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are
|
|
100
100
|
* rejected before signature verification even begins.
|
|
101
|
-
* • Issuer — must be exactly "
|
|
101
|
+
* • Issuer — must be exactly "agentid".
|
|
102
102
|
* • Expiry — enforced; configurable clock tolerance (default 30 s).
|
|
103
103
|
* • kid — jose matches the JWT `kid` header to the JWKS automatically.
|
|
104
104
|
* • auth_method — validated at runtime; must equal "bankid".
|
|
105
105
|
*
|
|
106
106
|
* @throws if the token is invalid, expired, or fails any check.
|
|
107
107
|
*/
|
|
108
|
-
declare function
|
|
108
|
+
declare function verifyAgentIDToken(token: string, options?: VerifyTokenOptions): Promise<AgentIDClaims>;
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* Bot / AI-agent detection for Next.js (Edge Runtime compatible).
|
|
@@ -128,36 +128,36 @@ declare function verifyAgentPassToken(token: string, options?: VerifyTokenOption
|
|
|
128
128
|
*/
|
|
129
129
|
declare function isAgentRequest(headers: Headers): boolean;
|
|
130
130
|
/**
|
|
131
|
-
* Extract the
|
|
131
|
+
* Extract the AgentID JWT from request headers.
|
|
132
132
|
*
|
|
133
133
|
* Checks in order:
|
|
134
134
|
* 1. `Authorization: Bearer <token>` (preferred)
|
|
135
|
-
* 2. `X-
|
|
135
|
+
* 2. `X-AgentID-Token` (fallback when Authorization is stripped by proxies)
|
|
136
136
|
*
|
|
137
137
|
* @returns The raw JWT string, or `null` if absent.
|
|
138
138
|
*/
|
|
139
139
|
declare function extractToken(headers: Headers): string | null;
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
* Extract the verified
|
|
142
|
+
* Extract the verified AgentID identity from a Next.js App Router request.
|
|
143
143
|
*
|
|
144
|
-
* This function reads the headers set by `
|
|
145
|
-
* only be called from route handlers that sit behind the
|
|
144
|
+
* This function reads the headers set by `createAgentIDMiddleware`. It must
|
|
145
|
+
* only be called from route handlers that sit behind the proxy.
|
|
146
146
|
*
|
|
147
147
|
* @example
|
|
148
148
|
* ```ts
|
|
149
149
|
* // app/api/data/route.ts
|
|
150
|
-
* import {
|
|
150
|
+
* import { getAgentIDResult } from '@agent-id/nextjs';
|
|
151
151
|
*
|
|
152
152
|
* export async function GET(request: NextRequest) {
|
|
153
|
-
* const
|
|
154
|
-
* if (!
|
|
153
|
+
* const agent = getAgentIDResult(request);
|
|
154
|
+
* if (!agent.verified) {
|
|
155
155
|
* return Response.json({ error: 'Unauthorized' }, { status: 403 });
|
|
156
156
|
* }
|
|
157
|
-
* return Response.json({ sub:
|
|
157
|
+
* return Response.json({ sub: agent.claims.sub });
|
|
158
158
|
* }
|
|
159
159
|
* ```
|
|
160
160
|
*/
|
|
161
|
-
declare function
|
|
161
|
+
declare function getAgentIDResult(request: NextRequest): AgentIDResult;
|
|
162
162
|
|
|
163
|
-
export { type
|
|
163
|
+
export { type AgentIDClaims, type AgentIDMiddlewareOptions, type AgentIDResult, type AgentIDUnverified, type AgentIDVerified, type VerifierOptions, type VerifyTokenOptions, createAgentIDMiddleware, extractToken, getAgentIDResult, isAgentRequest, verifyAgentIDToken };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* JWT claims present in every
|
|
4
|
+
* JWT claims present in every AgentID token.
|
|
5
5
|
*
|
|
6
6
|
* The `sub` field is a pseudonymous identifier derived via HMAC-SHA256 of the
|
|
7
7
|
* user's BankID personal number. It is stable per person but non-reversible —
|
|
8
8
|
* organisations cannot extract the personal number from it.
|
|
9
9
|
*/
|
|
10
|
-
interface
|
|
10
|
+
interface AgentIDClaims {
|
|
11
11
|
/** Pseudonymous, stable per-user identifier (HMAC-SHA256 of personal number). */
|
|
12
12
|
sub: string;
|
|
13
|
-
/** Issuer — always "
|
|
13
|
+
/** Issuer — always "agentid". */
|
|
14
14
|
iss: string;
|
|
15
15
|
/** Issued-at Unix timestamp. */
|
|
16
16
|
iat: number;
|
|
@@ -21,27 +21,27 @@ interface AgentPassClaims {
|
|
|
21
21
|
/** Authentication method — always "bankid" in this version. */
|
|
22
22
|
auth_method: "bankid";
|
|
23
23
|
}
|
|
24
|
-
interface
|
|
24
|
+
interface AgentIDVerified {
|
|
25
25
|
verified: true;
|
|
26
|
-
claims:
|
|
26
|
+
claims: AgentIDClaims;
|
|
27
27
|
}
|
|
28
|
-
interface
|
|
28
|
+
interface AgentIDUnverified {
|
|
29
29
|
verified: false;
|
|
30
30
|
/** Why verification was skipped or failed. */
|
|
31
31
|
reason: "not_agent" | "no_token" | "invalid_token";
|
|
32
32
|
}
|
|
33
|
-
/** Result attached to every request processed by the
|
|
34
|
-
type
|
|
33
|
+
/** Result attached to every request processed by the AgentID middleware. */
|
|
34
|
+
type AgentIDResult = AgentIDVerified | AgentIDUnverified;
|
|
35
35
|
interface VerifierOptions {
|
|
36
36
|
/**
|
|
37
|
-
* URL of the
|
|
37
|
+
* URL of the AgentID JWKS endpoint.
|
|
38
38
|
* Must use HTTPS (except `localhost` / `127.0.0.1` in development).
|
|
39
39
|
*
|
|
40
40
|
* @default 'https://agentpass.vercel.app/api/jwks'
|
|
41
41
|
*/
|
|
42
42
|
jwksUrl?: string;
|
|
43
43
|
/**
|
|
44
|
-
* Block agent requests that carry no valid
|
|
44
|
+
* Block agent requests that carry no valid AgentID token.
|
|
45
45
|
* When `false` the middleware still runs but sets an unverified result
|
|
46
46
|
* on the request instead of returning 403.
|
|
47
47
|
*
|
|
@@ -57,7 +57,7 @@ interface VerifierOptions {
|
|
|
57
57
|
clockTolerance?: number;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
type
|
|
60
|
+
type AgentIDMiddlewareOptions = VerifierOptions & {
|
|
61
61
|
/**
|
|
62
62
|
* Optional callback invoked when an agent is detected but carries no
|
|
63
63
|
* valid token. Return a `NextResponse` to override the default 403.
|
|
@@ -65,47 +65,47 @@ type AgentPassMiddlewareOptions = VerifierOptions & {
|
|
|
65
65
|
onUnauthorizedAgent?: (request: NextRequest, reason: string) => NextResponse | void;
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
|
-
* Factory that returns a Next.js
|
|
69
|
-
* traffic and enforces
|
|
68
|
+
* Factory that returns a Next.js proxy function which detects AI-agent
|
|
69
|
+
* traffic and enforces AgentID JWT authentication.
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
72
|
* ```ts
|
|
73
|
-
* //
|
|
74
|
-
* import {
|
|
73
|
+
* // proxy.ts (project root)
|
|
74
|
+
* import { createAgentIDMiddleware } from '@agent-id/nextjs';
|
|
75
75
|
*
|
|
76
|
-
* const
|
|
76
|
+
* const agentID = createAgentIDMiddleware({
|
|
77
77
|
* blockUnauthorizedAgents: true,
|
|
78
78
|
* });
|
|
79
79
|
*
|
|
80
|
-
* export function
|
|
81
|
-
* return
|
|
80
|
+
* export function proxy(request: NextRequest) {
|
|
81
|
+
* return agentID(request);
|
|
82
82
|
* }
|
|
83
83
|
*
|
|
84
84
|
* export const config = { matcher: '/api/:path*' };
|
|
85
85
|
* ```
|
|
86
86
|
*/
|
|
87
|
-
declare function
|
|
87
|
+
declare function createAgentIDMiddleware(options?: AgentIDMiddlewareOptions): (request: NextRequest) => Promise<NextResponse>;
|
|
88
88
|
|
|
89
89
|
interface VerifyTokenOptions {
|
|
90
90
|
jwksUrl?: string;
|
|
91
91
|
clockTolerance?: number;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
* Verify an
|
|
94
|
+
* Verify an AgentID JWT and return its decoded claims.
|
|
95
95
|
*
|
|
96
96
|
* Security guarantees
|
|
97
97
|
* ───────────────────
|
|
98
98
|
* • Signature — RS256, verified against the live JWKS public key.
|
|
99
99
|
* • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are
|
|
100
100
|
* rejected before signature verification even begins.
|
|
101
|
-
* • Issuer — must be exactly "
|
|
101
|
+
* • Issuer — must be exactly "agentid".
|
|
102
102
|
* • Expiry — enforced; configurable clock tolerance (default 30 s).
|
|
103
103
|
* • kid — jose matches the JWT `kid` header to the JWKS automatically.
|
|
104
104
|
* • auth_method — validated at runtime; must equal "bankid".
|
|
105
105
|
*
|
|
106
106
|
* @throws if the token is invalid, expired, or fails any check.
|
|
107
107
|
*/
|
|
108
|
-
declare function
|
|
108
|
+
declare function verifyAgentIDToken(token: string, options?: VerifyTokenOptions): Promise<AgentIDClaims>;
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* Bot / AI-agent detection for Next.js (Edge Runtime compatible).
|
|
@@ -128,36 +128,36 @@ declare function verifyAgentPassToken(token: string, options?: VerifyTokenOption
|
|
|
128
128
|
*/
|
|
129
129
|
declare function isAgentRequest(headers: Headers): boolean;
|
|
130
130
|
/**
|
|
131
|
-
* Extract the
|
|
131
|
+
* Extract the AgentID JWT from request headers.
|
|
132
132
|
*
|
|
133
133
|
* Checks in order:
|
|
134
134
|
* 1. `Authorization: Bearer <token>` (preferred)
|
|
135
|
-
* 2. `X-
|
|
135
|
+
* 2. `X-AgentID-Token` (fallback when Authorization is stripped by proxies)
|
|
136
136
|
*
|
|
137
137
|
* @returns The raw JWT string, or `null` if absent.
|
|
138
138
|
*/
|
|
139
139
|
declare function extractToken(headers: Headers): string | null;
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
* Extract the verified
|
|
142
|
+
* Extract the verified AgentID identity from a Next.js App Router request.
|
|
143
143
|
*
|
|
144
|
-
* This function reads the headers set by `
|
|
145
|
-
* only be called from route handlers that sit behind the
|
|
144
|
+
* This function reads the headers set by `createAgentIDMiddleware`. It must
|
|
145
|
+
* only be called from route handlers that sit behind the proxy.
|
|
146
146
|
*
|
|
147
147
|
* @example
|
|
148
148
|
* ```ts
|
|
149
149
|
* // app/api/data/route.ts
|
|
150
|
-
* import {
|
|
150
|
+
* import { getAgentIDResult } from '@agent-id/nextjs';
|
|
151
151
|
*
|
|
152
152
|
* export async function GET(request: NextRequest) {
|
|
153
|
-
* const
|
|
154
|
-
* if (!
|
|
153
|
+
* const agent = getAgentIDResult(request);
|
|
154
|
+
* if (!agent.verified) {
|
|
155
155
|
* return Response.json({ error: 'Unauthorized' }, { status: 403 });
|
|
156
156
|
* }
|
|
157
|
-
* return Response.json({ sub:
|
|
157
|
+
* return Response.json({ sub: agent.claims.sub });
|
|
158
158
|
* }
|
|
159
159
|
* ```
|
|
160
160
|
*/
|
|
161
|
-
declare function
|
|
161
|
+
declare function getAgentIDResult(request: NextRequest): AgentIDResult;
|
|
162
162
|
|
|
163
|
-
export { type
|
|
163
|
+
export { type AgentIDClaims, type AgentIDMiddlewareOptions, type AgentIDResult, type AgentIDUnverified, type AgentIDVerified, type VerifierOptions, type VerifyTokenOptions, createAgentIDMiddleware, extractToken, getAgentIDResult, isAgentRequest, verifyAgentIDToken };
|
package/dist/index.js
CHANGED
|
@@ -20,11 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
createAgentIDMiddleware: () => createAgentIDMiddleware,
|
|
24
24
|
extractToken: () => extractToken,
|
|
25
|
-
|
|
25
|
+
getAgentIDResult: () => getAgentIDResult,
|
|
26
26
|
isAgentRequest: () => isAgentRequest,
|
|
27
|
-
|
|
27
|
+
verifyAgentIDToken: () => verifyAgentIDToken
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(index_exports);
|
|
30
30
|
|
|
@@ -41,6 +41,7 @@ var BOT_UA_PATTERNS = [
|
|
|
41
41
|
/ClaudeBot/i,
|
|
42
42
|
/Claude-Web/i,
|
|
43
43
|
/anthropic-ai/i,
|
|
44
|
+
/Claude-User/i,
|
|
44
45
|
// Google
|
|
45
46
|
/Googlebot/i,
|
|
46
47
|
/Google-Extended/i,
|
|
@@ -66,9 +67,9 @@ var BOT_UA_PATTERNS = [
|
|
|
66
67
|
/\bspider\b/i,
|
|
67
68
|
/\bscraper\b/i,
|
|
68
69
|
/\bfetcher\b/i,
|
|
69
|
-
// MCP /
|
|
70
|
+
// MCP / AgentID clients
|
|
70
71
|
/mcp-client/i,
|
|
71
|
-
/
|
|
72
|
+
/agentid-client/i
|
|
72
73
|
];
|
|
73
74
|
var BROWSER_UA_RE = /Mozilla\/5\.0/i;
|
|
74
75
|
function isAgentRequest(headers) {
|
|
@@ -92,7 +93,7 @@ function extractToken(headers) {
|
|
|
92
93
|
const token = auth.slice(7).trim();
|
|
93
94
|
if (token) return token;
|
|
94
95
|
}
|
|
95
|
-
const custom = headers.get("x-
|
|
96
|
+
const custom = headers.get("x-agentid-token")?.trim();
|
|
96
97
|
if (custom) return custom;
|
|
97
98
|
return null;
|
|
98
99
|
}
|
|
@@ -120,11 +121,11 @@ function getJwks(rawUrl) {
|
|
|
120
121
|
}
|
|
121
122
|
return jwksSets.get(rawUrl);
|
|
122
123
|
}
|
|
123
|
-
async function
|
|
124
|
+
async function verifyAgentIDToken(token, options = {}) {
|
|
124
125
|
const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;
|
|
125
126
|
const JWKS = getJwks(jwksUrl);
|
|
126
127
|
const { payload } = await (0, import_jose.jwtVerify)(token, JWKS, {
|
|
127
|
-
issuer: "
|
|
128
|
+
issuer: "agentid",
|
|
128
129
|
// ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.
|
|
129
130
|
// Any token claiming alg:"none", alg:"HS256", or anything else is
|
|
130
131
|
// rejected before signature verification.
|
|
@@ -144,18 +145,18 @@ async function verifyAgentPassToken(token, options = {}) {
|
|
|
144
145
|
|
|
145
146
|
// src/middleware.ts
|
|
146
147
|
var MANAGED_HEADERS = [
|
|
147
|
-
"x-
|
|
148
|
-
"x-
|
|
149
|
-
"x-
|
|
148
|
+
"x-agentid-verified",
|
|
149
|
+
"x-agentid-sub",
|
|
150
|
+
"x-agentid-claims"
|
|
150
151
|
];
|
|
151
|
-
function
|
|
152
|
+
function createAgentIDMiddleware(options = {}) {
|
|
152
153
|
const {
|
|
153
154
|
jwksUrl,
|
|
154
155
|
blockUnauthorizedAgents = true,
|
|
155
156
|
clockTolerance = 30,
|
|
156
157
|
onUnauthorizedAgent
|
|
157
158
|
} = options;
|
|
158
|
-
return async function
|
|
159
|
+
return async function agentIDMiddleware(request) {
|
|
159
160
|
const requestHeaders = new Headers(request.headers);
|
|
160
161
|
for (const name of MANAGED_HEADERS) {
|
|
161
162
|
requestHeaders.delete(name);
|
|
@@ -168,14 +169,14 @@ function createAgentPassMiddleware(options = {}) {
|
|
|
168
169
|
return unauthorized(
|
|
169
170
|
request,
|
|
170
171
|
requestHeaders,
|
|
171
|
-
'Agent request missing
|
|
172
|
+
'Agent request missing AgentID token. Provide "Authorization: Bearer <token>".',
|
|
172
173
|
blockUnauthorizedAgents,
|
|
173
174
|
onUnauthorizedAgent
|
|
174
175
|
);
|
|
175
176
|
}
|
|
176
177
|
let claims;
|
|
177
178
|
try {
|
|
178
|
-
claims = await
|
|
179
|
+
claims = await verifyAgentIDToken(token, {
|
|
179
180
|
...jwksUrl !== void 0 && { jwksUrl },
|
|
180
181
|
clockTolerance
|
|
181
182
|
});
|
|
@@ -187,14 +188,14 @@ function createAgentPassMiddleware(options = {}) {
|
|
|
187
188
|
return unauthorized(
|
|
188
189
|
request,
|
|
189
190
|
requestHeaders,
|
|
190
|
-
"Invalid or expired
|
|
191
|
+
"Invalid or expired AgentID token.",
|
|
191
192
|
blockUnauthorizedAgents,
|
|
192
193
|
onUnauthorizedAgent
|
|
193
194
|
);
|
|
194
195
|
}
|
|
195
|
-
requestHeaders.set("x-
|
|
196
|
-
requestHeaders.set("x-
|
|
197
|
-
requestHeaders.set("x-
|
|
196
|
+
requestHeaders.set("x-agentid-verified", "true");
|
|
197
|
+
requestHeaders.set("x-agentid-sub", claims.sub);
|
|
198
|
+
requestHeaders.set("x-agentid-claims", JSON.stringify(claims));
|
|
198
199
|
return import_server.NextResponse.next({ request: { headers: requestHeaders } });
|
|
199
200
|
};
|
|
200
201
|
}
|
|
@@ -209,20 +210,20 @@ function unauthorized(request, requestHeaders, message, block, onUnauthorized) {
|
|
|
209
210
|
{ status: 403 }
|
|
210
211
|
);
|
|
211
212
|
}
|
|
212
|
-
requestHeaders.set("x-
|
|
213
|
+
requestHeaders.set("x-agentid-verified", "false");
|
|
213
214
|
return import_server.NextResponse.next({ request: { headers: requestHeaders } });
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
// src/index.ts
|
|
217
|
-
function
|
|
218
|
-
const verified = request.headers.get("x-
|
|
218
|
+
function getAgentIDResult(request) {
|
|
219
|
+
const verified = request.headers.get("x-agentid-verified");
|
|
219
220
|
if (verified !== "true") {
|
|
220
221
|
return {
|
|
221
222
|
verified: false,
|
|
222
223
|
reason: verified === "false" ? "no_token" : "not_agent"
|
|
223
224
|
};
|
|
224
225
|
}
|
|
225
|
-
const claimsJson = request.headers.get("x-
|
|
226
|
+
const claimsJson = request.headers.get("x-agentid-claims");
|
|
226
227
|
if (!claimsJson) {
|
|
227
228
|
return { verified: false, reason: "invalid_token" };
|
|
228
229
|
}
|
|
@@ -235,10 +236,10 @@ function getAgentPassResult(request) {
|
|
|
235
236
|
}
|
|
236
237
|
// Annotate the CommonJS export names for ESM import in node:
|
|
237
238
|
0 && (module.exports = {
|
|
238
|
-
|
|
239
|
+
createAgentIDMiddleware,
|
|
239
240
|
extractToken,
|
|
240
|
-
|
|
241
|
+
getAgentIDResult,
|
|
241
242
|
isAgentRequest,
|
|
242
|
-
|
|
243
|
+
verifyAgentIDToken
|
|
243
244
|
});
|
|
244
245
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/detect.ts","../src/verify.ts"],"sourcesContent":["export { createAgentPassMiddleware } from \"./middleware.js\";\nexport type { AgentPassMiddlewareOptions } from \"./middleware.js\";\n\nexport { verifyAgentPassToken } from \"./verify.js\";\nexport type { VerifyTokenOptions } from \"./verify.js\";\n\nexport { isAgentRequest, extractToken } from \"./detect.js\";\n\nexport type {\n AgentPassClaims,\n AgentPassResult,\n AgentPassVerified,\n AgentPassUnverified,\n VerifierOptions,\n} from \"./types.js\";\n\n// ── App Router helper ────────────────────────────────────────────────────────\n\nimport type { NextRequest } from \"next/server\";\nimport type { AgentPassResult, AgentPassClaims } from \"./types.js\";\n\n/**\n * Extract the verified AgentPass identity from a Next.js App Router request.\n *\n * This function reads the headers set by `createAgentPassMiddleware`. It must\n * only be called from route handlers that sit behind the middleware.\n *\n * @example\n * ```ts\n * // app/api/data/route.ts\n * import { getAgentPassResult } from '@agent-id/nextjs';\n *\n * export async function GET(request: NextRequest) {\n * const agentPass = getAgentPassResult(request);\n * if (!agentPass.verified) {\n * return Response.json({ error: 'Unauthorized' }, { status: 403 });\n * }\n * return Response.json({ sub: agentPass.claims.sub });\n * }\n * ```\n */\nexport function getAgentPassResult(request: NextRequest): AgentPassResult {\n const verified = request.headers.get(\"x-agentpass-verified\");\n\n if (verified !== \"true\") {\n return {\n verified: false,\n reason: verified === \"false\" ? \"no_token\" : \"not_agent\",\n };\n }\n\n const claimsJson = request.headers.get(\"x-agentpass-claims\");\n if (!claimsJson) {\n return { verified: false, reason: \"invalid_token\" };\n }\n\n try {\n const claims = JSON.parse(claimsJson) as AgentPassClaims;\n return { verified: true, claims };\n } catch {\n return { verified: false, reason: \"invalid_token\" };\n }\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentPassToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentPassClaims } from \"./types.js\";\n\nexport type AgentPassMiddlewareOptions = VerifierOptions & {\n /**\n * Optional callback invoked when an agent is detected but carries no\n * valid token. Return a `NextResponse` to override the default 403.\n */\n onUnauthorizedAgent?: (\n request: NextRequest,\n reason: string\n ) => NextResponse | void;\n};\n\n/**\n * Headers injected by this middleware into downstream route handlers.\n * They are stripped from the *incoming* request first to prevent spoofing.\n */\nconst MANAGED_HEADERS = [\n \"x-agentpass-verified\",\n \"x-agentpass-sub\",\n \"x-agentpass-claims\",\n] as const;\n\n/**\n * Factory that returns a Next.js middleware function which detects AI-agent\n * traffic and enforces AgentPass JWT authentication.\n *\n * @example\n * ```ts\n * // middleware.ts (project root)\n * import { createAgentPassMiddleware } from '@agent-id/nextjs';\n *\n * const agentPass = createAgentPassMiddleware({\n * blockUnauthorizedAgents: true,\n * });\n *\n * export function middleware(request: NextRequest) {\n * return agentPass(request);\n * }\n *\n * export const config = { matcher: '/api/:path*' };\n * ```\n */\nexport function createAgentPassMiddleware(\n options: AgentPassMiddlewareOptions = {}\n) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentPassMiddleware(\n request: NextRequest\n ): Promise<NextResponse> {\n // Clone incoming headers so we can safely mutate them.\n const requestHeaders = new Headers(request.headers);\n\n // ── Security: strip client-supplied AgentPass headers ──────────────────\n // Without this, a malicious agent could send x-agentpass-verified: true\n // and bypass the check in route handlers.\n for (const name of MANAGED_HEADERS) {\n requestHeaders.delete(name);\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(requestHeaders)) {\n return NextResponse.next({ request: { headers: requestHeaders } });\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(requestHeaders);\n\n if (!token) {\n return unauthorized(\n request,\n requestHeaders,\n 'Agent request missing AgentPass token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentPassClaims;\n try {\n claims = await verifyAgentPassToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n // Never surface the raw error to the caller — it might leak internals.\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return unauthorized(\n request,\n requestHeaders,\n \"Invalid or expired AgentPass token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified — inject identity into request headers ────────────────────\n // Route handlers read these via getAgentPassResult(request).\n requestHeaders.set(\"x-agentpass-verified\", \"true\");\n requestHeaders.set(\"x-agentpass-sub\", claims.sub);\n // Full claims encoded as JSON for type-safe extraction by helpers.\n requestHeaders.set(\"x-agentpass-claims\", JSON.stringify(claims));\n\n return NextResponse.next({ request: { headers: requestHeaders } });\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction unauthorized(\n request: NextRequest,\n requestHeaders: Headers,\n message: string,\n block: boolean,\n onUnauthorized: AgentPassMiddlewareOptions[\"onUnauthorizedAgent\"]\n): NextResponse {\n if (onUnauthorized) {\n const custom = onUnauthorized(request, message);\n if (custom) return custom;\n }\n\n if (block) {\n return NextResponse.json(\n { error: \"AGENT_UNAUTHORIZED\", message },\n { status: 403 }\n );\n }\n\n // Pass through with a \"not verified\" marker so route handlers can still\n // distinguish agent traffic from human traffic.\n requestHeaders.set(\"x-agentpass-verified\", \"false\");\n return NextResponse.next({ request: { headers: requestHeaders } });\n}\n","/**\n * Bot / AI-agent detection for Next.js (Edge Runtime compatible).\n *\n * Uses layered heuristics:\n * 1. Explicit bot / AI-agent User-Agent strings\n * 2. Cloudflare Bot Management score (if header is present)\n * 3. Absence of browser signals (no Accept-Language + non-browser UA)\n *\n * The detector is intentionally conservative — when in doubt it lets the\n * request through (fail open). A false negative (undetected bot) means the\n * request passes without a JWT check. A false positive (human flagged as bot)\n * would block legitimate traffic, which is much worse.\n */\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals (word-boundary matched to reduce false positives)\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentPass clients\n /mcp-client/i,\n /agentpass-client/i,\n];\n\n// Every major browser includes \"Mozilla/5.0\" — its absence is a strong signal\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - The `Headers` object from a Next.js `NextRequest`\n */\nexport function isAgentRequest(headers: Headers): boolean {\n const ua = headers.get(\"user-agent\") ?? \"\";\n\n // No User-Agent → definitely automated\n if (!ua) return true;\n\n // Explicit bot / agent UA match\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management: the `cf-bot-management` header contains a\n // JSON blob with a `score` field (0–100). Score < 30 → highly likely bot.\n const cfRaw = headers.get(\"cf-bot-management\");\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open (pass through)\n }\n }\n\n // Heuristic: non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers.get(\"accept-language\")) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentPass JWT from request headers.\n *\n * Checks in order:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentPass-Token` (fallback when Authorization is stripped by proxies)\n *\n * @returns The raw JWT string, or `null` if absent.\n */\nexport function extractToken(headers: Headers): string | null {\n // Primary: standard Authorization header\n const auth = headers.get(\"authorization\") ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n // Fallback: custom header\n const custom = headers.get(\"x-agentpass-token\")?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentPassClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentpass.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentPass JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentpass\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentPassToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentPassClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentpass\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentPass-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentPassClaims;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/middleware.ts","../src/detect.ts","../src/verify.ts"],"sourcesContent":["export { createAgentIDMiddleware } from \"./middleware.js\";\nexport type { AgentIDMiddlewareOptions } from \"./middleware.js\";\n\nexport { verifyAgentIDToken } from \"./verify.js\";\nexport type { VerifyTokenOptions } from \"./verify.js\";\n\nexport { isAgentRequest, extractToken } from \"./detect.js\";\n\nexport type {\n AgentIDClaims,\n AgentIDResult,\n AgentIDVerified,\n AgentIDUnverified,\n VerifierOptions,\n} from \"./types.js\";\n\n// ── App Router helper ────────────────────────────────────────────────────────\n\nimport type { NextRequest } from \"next/server\";\nimport type { AgentIDResult, AgentIDClaims } from \"./types.js\";\n\n/**\n * Extract the verified AgentID identity from a Next.js App Router request.\n *\n * This function reads the headers set by `createAgentIDMiddleware`. It must\n * only be called from route handlers that sit behind the proxy.\n *\n * @example\n * ```ts\n * // app/api/data/route.ts\n * import { getAgentIDResult } from '@agent-id/nextjs';\n *\n * export async function GET(request: NextRequest) {\n * const agent = getAgentIDResult(request);\n * if (!agent.verified) {\n * return Response.json({ error: 'Unauthorized' }, { status: 403 });\n * }\n * return Response.json({ sub: agent.claims.sub });\n * }\n * ```\n */\nexport function getAgentIDResult(request: NextRequest): AgentIDResult {\n const verified = request.headers.get(\"x-agentid-verified\");\n\n if (verified !== \"true\") {\n return {\n verified: false,\n reason: verified === \"false\" ? \"no_token\" : \"not_agent\",\n };\n }\n\n const claimsJson = request.headers.get(\"x-agentid-claims\");\n if (!claimsJson) {\n return { verified: false, reason: \"invalid_token\" };\n }\n\n try {\n const claims = JSON.parse(claimsJson) as AgentIDClaims;\n return { verified: true, claims };\n } catch {\n return { verified: false, reason: \"invalid_token\" };\n }\n}\n","import { NextRequest, NextResponse } from \"next/server\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentIDToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentIDClaims } from \"./types.js\";\n\nexport type AgentIDMiddlewareOptions = VerifierOptions & {\n /**\n * Optional callback invoked when an agent is detected but carries no\n * valid token. Return a `NextResponse` to override the default 403.\n */\n onUnauthorizedAgent?: (\n request: NextRequest,\n reason: string\n ) => NextResponse | void;\n};\n\n/**\n * Headers injected by this proxy into downstream route handlers.\n * They are stripped from the *incoming* request first to prevent spoofing.\n */\nconst MANAGED_HEADERS = [\n \"x-agentid-verified\",\n \"x-agentid-sub\",\n \"x-agentid-claims\",\n] as const;\n\n/**\n * Factory that returns a Next.js proxy function which detects AI-agent\n * traffic and enforces AgentID JWT authentication.\n *\n * @example\n * ```ts\n * // proxy.ts (project root)\n * import { createAgentIDMiddleware } from '@agent-id/nextjs';\n *\n * const agentID = createAgentIDMiddleware({\n * blockUnauthorizedAgents: true,\n * });\n *\n * export function proxy(request: NextRequest) {\n * return agentID(request);\n * }\n *\n * export const config = { matcher: '/api/:path*' };\n * ```\n */\nexport function createAgentIDMiddleware(\n options: AgentIDMiddlewareOptions = {}\n) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentIDMiddleware(\n request: NextRequest\n ): Promise<NextResponse> {\n // Clone incoming headers so we can safely mutate them.\n const requestHeaders = new Headers(request.headers);\n\n // ── Security: strip client-supplied AgentID headers ──────────────────\n // Without this, a malicious agent could send x-agentid-verified: true\n // and bypass the check in route handlers.\n for (const name of MANAGED_HEADERS) {\n requestHeaders.delete(name);\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(requestHeaders)) {\n return NextResponse.next({ request: { headers: requestHeaders } });\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(requestHeaders);\n\n if (!token) {\n return unauthorized(\n request,\n requestHeaders,\n 'Agent request missing AgentID token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentIDClaims;\n try {\n claims = await verifyAgentIDToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n // Never surface the raw error to the caller — it might leak internals.\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return unauthorized(\n request,\n requestHeaders,\n \"Invalid or expired AgentID token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified — inject identity into request headers ────────────────────\n // Route handlers read these via getAgentIDResult(request).\n requestHeaders.set(\"x-agentid-verified\", \"true\");\n requestHeaders.set(\"x-agentid-sub\", claims.sub);\n // Full claims encoded as JSON for type-safe extraction by helpers.\n requestHeaders.set(\"x-agentid-claims\", JSON.stringify(claims));\n\n return NextResponse.next({ request: { headers: requestHeaders } });\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction unauthorized(\n request: NextRequest,\n requestHeaders: Headers,\n message: string,\n block: boolean,\n onUnauthorized: AgentIDMiddlewareOptions[\"onUnauthorizedAgent\"]\n): NextResponse {\n if (onUnauthorized) {\n const custom = onUnauthorized(request, message);\n if (custom) return custom;\n }\n\n if (block) {\n return NextResponse.json(\n { error: \"AGENT_UNAUTHORIZED\", message },\n { status: 403 }\n );\n }\n\n // Pass through with a \"not verified\" marker so route handlers can still\n // distinguish agent traffic from human traffic.\n requestHeaders.set(\"x-agentid-verified\", \"false\");\n return NextResponse.next({ request: { headers: requestHeaders } });\n}\n","/**\n * Bot / AI-agent detection for Next.js (Edge Runtime compatible).\n *\n * Uses layered heuristics:\n * 1. Explicit bot / AI-agent User-Agent strings\n * 2. Cloudflare Bot Management score (if header is present)\n * 3. Absence of browser signals (no Accept-Language + non-browser UA)\n *\n * The detector is intentionally conservative — when in doubt it lets the\n * request through (fail open). A false negative (undetected bot) means the\n * request passes without a JWT check. A false positive (human flagged as bot)\n * would block legitimate traffic, which is much worse.\n */\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n /Claude-User/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals (word-boundary matched to reduce false positives)\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentID clients\n /mcp-client/i,\n /agentid-client/i,\n];\n\n// Every major browser includes \"Mozilla/5.0\" — its absence is a strong signal\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - The `Headers` object from a Next.js `NextRequest`\n */\nexport function isAgentRequest(headers: Headers): boolean {\n const ua = headers.get(\"user-agent\") ?? \"\";\n\n // No User-Agent → definitely automated\n if (!ua) return true;\n\n // Explicit bot / agent UA match\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management: the `cf-bot-management` header contains a\n // JSON blob with a `score` field (0–100). Score < 30 → highly likely bot.\n const cfRaw = headers.get(\"cf-bot-management\");\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open (pass through)\n }\n }\n\n // Heuristic: non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers.get(\"accept-language\")) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentID JWT from request headers.\n *\n * Checks in order:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentID-Token` (fallback when Authorization is stripped by proxies)\n *\n * @returns The raw JWT string, or `null` if absent.\n */\nexport function extractToken(headers: Headers): string | null {\n // Primary: standard Authorization header\n const auth = headers.get(\"authorization\") ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n // Fallback: custom header\n const custom = headers.get(\"x-agentid-token\")?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentIDClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentpass.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentID JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentid\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentIDToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentIDClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentid\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentID-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentIDClaims;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,7 @@ var BOT_UA_PATTERNS = [
|
|
|
11
11
|
/ClaudeBot/i,
|
|
12
12
|
/Claude-Web/i,
|
|
13
13
|
/anthropic-ai/i,
|
|
14
|
+
/Claude-User/i,
|
|
14
15
|
// Google
|
|
15
16
|
/Googlebot/i,
|
|
16
17
|
/Google-Extended/i,
|
|
@@ -36,9 +37,9 @@ var BOT_UA_PATTERNS = [
|
|
|
36
37
|
/\bspider\b/i,
|
|
37
38
|
/\bscraper\b/i,
|
|
38
39
|
/\bfetcher\b/i,
|
|
39
|
-
// MCP /
|
|
40
|
+
// MCP / AgentID clients
|
|
40
41
|
/mcp-client/i,
|
|
41
|
-
/
|
|
42
|
+
/agentid-client/i
|
|
42
43
|
];
|
|
43
44
|
var BROWSER_UA_RE = /Mozilla\/5\.0/i;
|
|
44
45
|
function isAgentRequest(headers) {
|
|
@@ -62,7 +63,7 @@ function extractToken(headers) {
|
|
|
62
63
|
const token = auth.slice(7).trim();
|
|
63
64
|
if (token) return token;
|
|
64
65
|
}
|
|
65
|
-
const custom = headers.get("x-
|
|
66
|
+
const custom = headers.get("x-agentid-token")?.trim();
|
|
66
67
|
if (custom) return custom;
|
|
67
68
|
return null;
|
|
68
69
|
}
|
|
@@ -90,11 +91,11 @@ function getJwks(rawUrl) {
|
|
|
90
91
|
}
|
|
91
92
|
return jwksSets.get(rawUrl);
|
|
92
93
|
}
|
|
93
|
-
async function
|
|
94
|
+
async function verifyAgentIDToken(token, options = {}) {
|
|
94
95
|
const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;
|
|
95
96
|
const JWKS = getJwks(jwksUrl);
|
|
96
97
|
const { payload } = await jwtVerify(token, JWKS, {
|
|
97
|
-
issuer: "
|
|
98
|
+
issuer: "agentid",
|
|
98
99
|
// ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.
|
|
99
100
|
// Any token claiming alg:"none", alg:"HS256", or anything else is
|
|
100
101
|
// rejected before signature verification.
|
|
@@ -114,18 +115,18 @@ async function verifyAgentPassToken(token, options = {}) {
|
|
|
114
115
|
|
|
115
116
|
// src/middleware.ts
|
|
116
117
|
var MANAGED_HEADERS = [
|
|
117
|
-
"x-
|
|
118
|
-
"x-
|
|
119
|
-
"x-
|
|
118
|
+
"x-agentid-verified",
|
|
119
|
+
"x-agentid-sub",
|
|
120
|
+
"x-agentid-claims"
|
|
120
121
|
];
|
|
121
|
-
function
|
|
122
|
+
function createAgentIDMiddleware(options = {}) {
|
|
122
123
|
const {
|
|
123
124
|
jwksUrl,
|
|
124
125
|
blockUnauthorizedAgents = true,
|
|
125
126
|
clockTolerance = 30,
|
|
126
127
|
onUnauthorizedAgent
|
|
127
128
|
} = options;
|
|
128
|
-
return async function
|
|
129
|
+
return async function agentIDMiddleware(request) {
|
|
129
130
|
const requestHeaders = new Headers(request.headers);
|
|
130
131
|
for (const name of MANAGED_HEADERS) {
|
|
131
132
|
requestHeaders.delete(name);
|
|
@@ -138,14 +139,14 @@ function createAgentPassMiddleware(options = {}) {
|
|
|
138
139
|
return unauthorized(
|
|
139
140
|
request,
|
|
140
141
|
requestHeaders,
|
|
141
|
-
'Agent request missing
|
|
142
|
+
'Agent request missing AgentID token. Provide "Authorization: Bearer <token>".',
|
|
142
143
|
blockUnauthorizedAgents,
|
|
143
144
|
onUnauthorizedAgent
|
|
144
145
|
);
|
|
145
146
|
}
|
|
146
147
|
let claims;
|
|
147
148
|
try {
|
|
148
|
-
claims = await
|
|
149
|
+
claims = await verifyAgentIDToken(token, {
|
|
149
150
|
...jwksUrl !== void 0 && { jwksUrl },
|
|
150
151
|
clockTolerance
|
|
151
152
|
});
|
|
@@ -157,14 +158,14 @@ function createAgentPassMiddleware(options = {}) {
|
|
|
157
158
|
return unauthorized(
|
|
158
159
|
request,
|
|
159
160
|
requestHeaders,
|
|
160
|
-
"Invalid or expired
|
|
161
|
+
"Invalid or expired AgentID token.",
|
|
161
162
|
blockUnauthorizedAgents,
|
|
162
163
|
onUnauthorizedAgent
|
|
163
164
|
);
|
|
164
165
|
}
|
|
165
|
-
requestHeaders.set("x-
|
|
166
|
-
requestHeaders.set("x-
|
|
167
|
-
requestHeaders.set("x-
|
|
166
|
+
requestHeaders.set("x-agentid-verified", "true");
|
|
167
|
+
requestHeaders.set("x-agentid-sub", claims.sub);
|
|
168
|
+
requestHeaders.set("x-agentid-claims", JSON.stringify(claims));
|
|
168
169
|
return NextResponse.next({ request: { headers: requestHeaders } });
|
|
169
170
|
};
|
|
170
171
|
}
|
|
@@ -179,20 +180,20 @@ function unauthorized(request, requestHeaders, message, block, onUnauthorized) {
|
|
|
179
180
|
{ status: 403 }
|
|
180
181
|
);
|
|
181
182
|
}
|
|
182
|
-
requestHeaders.set("x-
|
|
183
|
+
requestHeaders.set("x-agentid-verified", "false");
|
|
183
184
|
return NextResponse.next({ request: { headers: requestHeaders } });
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
// src/index.ts
|
|
187
|
-
function
|
|
188
|
-
const verified = request.headers.get("x-
|
|
188
|
+
function getAgentIDResult(request) {
|
|
189
|
+
const verified = request.headers.get("x-agentid-verified");
|
|
189
190
|
if (verified !== "true") {
|
|
190
191
|
return {
|
|
191
192
|
verified: false,
|
|
192
193
|
reason: verified === "false" ? "no_token" : "not_agent"
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
|
-
const claimsJson = request.headers.get("x-
|
|
196
|
+
const claimsJson = request.headers.get("x-agentid-claims");
|
|
196
197
|
if (!claimsJson) {
|
|
197
198
|
return { verified: false, reason: "invalid_token" };
|
|
198
199
|
}
|
|
@@ -204,10 +205,10 @@ function getAgentPassResult(request) {
|
|
|
204
205
|
}
|
|
205
206
|
}
|
|
206
207
|
export {
|
|
207
|
-
|
|
208
|
+
createAgentIDMiddleware,
|
|
208
209
|
extractToken,
|
|
209
|
-
|
|
210
|
+
getAgentIDResult,
|
|
210
211
|
isAgentRequest,
|
|
211
|
-
|
|
212
|
+
verifyAgentIDToken
|
|
212
213
|
};
|
|
213
214
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/middleware.ts","../src/detect.ts","../src/verify.ts","../src/index.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentPassToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentPassClaims } from \"./types.js\";\n\nexport type AgentPassMiddlewareOptions = VerifierOptions & {\n /**\n * Optional callback invoked when an agent is detected but carries no\n * valid token. Return a `NextResponse` to override the default 403.\n */\n onUnauthorizedAgent?: (\n request: NextRequest,\n reason: string\n ) => NextResponse | void;\n};\n\n/**\n * Headers injected by this middleware into downstream route handlers.\n * They are stripped from the *incoming* request first to prevent spoofing.\n */\nconst MANAGED_HEADERS = [\n \"x-agentpass-verified\",\n \"x-agentpass-sub\",\n \"x-agentpass-claims\",\n] as const;\n\n/**\n * Factory that returns a Next.js middleware function which detects AI-agent\n * traffic and enforces AgentPass JWT authentication.\n *\n * @example\n * ```ts\n * // middleware.ts (project root)\n * import { createAgentPassMiddleware } from '@agent-id/nextjs';\n *\n * const agentPass = createAgentPassMiddleware({\n * blockUnauthorizedAgents: true,\n * });\n *\n * export function middleware(request: NextRequest) {\n * return agentPass(request);\n * }\n *\n * export const config = { matcher: '/api/:path*' };\n * ```\n */\nexport function createAgentPassMiddleware(\n options: AgentPassMiddlewareOptions = {}\n) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentPassMiddleware(\n request: NextRequest\n ): Promise<NextResponse> {\n // Clone incoming headers so we can safely mutate them.\n const requestHeaders = new Headers(request.headers);\n\n // ── Security: strip client-supplied AgentPass headers ──────────────────\n // Without this, a malicious agent could send x-agentpass-verified: true\n // and bypass the check in route handlers.\n for (const name of MANAGED_HEADERS) {\n requestHeaders.delete(name);\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(requestHeaders)) {\n return NextResponse.next({ request: { headers: requestHeaders } });\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(requestHeaders);\n\n if (!token) {\n return unauthorized(\n request,\n requestHeaders,\n 'Agent request missing AgentPass token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentPassClaims;\n try {\n claims = await verifyAgentPassToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n // Never surface the raw error to the caller — it might leak internals.\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return unauthorized(\n request,\n requestHeaders,\n \"Invalid or expired AgentPass token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified — inject identity into request headers ────────────────────\n // Route handlers read these via getAgentPassResult(request).\n requestHeaders.set(\"x-agentpass-verified\", \"true\");\n requestHeaders.set(\"x-agentpass-sub\", claims.sub);\n // Full claims encoded as JSON for type-safe extraction by helpers.\n requestHeaders.set(\"x-agentpass-claims\", JSON.stringify(claims));\n\n return NextResponse.next({ request: { headers: requestHeaders } });\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction unauthorized(\n request: NextRequest,\n requestHeaders: Headers,\n message: string,\n block: boolean,\n onUnauthorized: AgentPassMiddlewareOptions[\"onUnauthorizedAgent\"]\n): NextResponse {\n if (onUnauthorized) {\n const custom = onUnauthorized(request, message);\n if (custom) return custom;\n }\n\n if (block) {\n return NextResponse.json(\n { error: \"AGENT_UNAUTHORIZED\", message },\n { status: 403 }\n );\n }\n\n // Pass through with a \"not verified\" marker so route handlers can still\n // distinguish agent traffic from human traffic.\n requestHeaders.set(\"x-agentpass-verified\", \"false\");\n return NextResponse.next({ request: { headers: requestHeaders } });\n}\n","/**\n * Bot / AI-agent detection for Next.js (Edge Runtime compatible).\n *\n * Uses layered heuristics:\n * 1. Explicit bot / AI-agent User-Agent strings\n * 2. Cloudflare Bot Management score (if header is present)\n * 3. Absence of browser signals (no Accept-Language + non-browser UA)\n *\n * The detector is intentionally conservative — when in doubt it lets the\n * request through (fail open). A false negative (undetected bot) means the\n * request passes without a JWT check. A false positive (human flagged as bot)\n * would block legitimate traffic, which is much worse.\n */\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals (word-boundary matched to reduce false positives)\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentPass clients\n /mcp-client/i,\n /agentpass-client/i,\n];\n\n// Every major browser includes \"Mozilla/5.0\" — its absence is a strong signal\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - The `Headers` object from a Next.js `NextRequest`\n */\nexport function isAgentRequest(headers: Headers): boolean {\n const ua = headers.get(\"user-agent\") ?? \"\";\n\n // No User-Agent → definitely automated\n if (!ua) return true;\n\n // Explicit bot / agent UA match\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management: the `cf-bot-management` header contains a\n // JSON blob with a `score` field (0–100). Score < 30 → highly likely bot.\n const cfRaw = headers.get(\"cf-bot-management\");\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open (pass through)\n }\n }\n\n // Heuristic: non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers.get(\"accept-language\")) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentPass JWT from request headers.\n *\n * Checks in order:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentPass-Token` (fallback when Authorization is stripped by proxies)\n *\n * @returns The raw JWT string, or `null` if absent.\n */\nexport function extractToken(headers: Headers): string | null {\n // Primary: standard Authorization header\n const auth = headers.get(\"authorization\") ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n // Fallback: custom header\n const custom = headers.get(\"x-agentpass-token\")?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentPassClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentpass.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentPass JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentpass\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentPassToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentPassClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentpass\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentPass-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentPassClaims;\n}\n","export { createAgentPassMiddleware } from \"./middleware.js\";\nexport type { AgentPassMiddlewareOptions } from \"./middleware.js\";\n\nexport { verifyAgentPassToken } from \"./verify.js\";\nexport type { VerifyTokenOptions } from \"./verify.js\";\n\nexport { isAgentRequest, extractToken } from \"./detect.js\";\n\nexport type {\n AgentPassClaims,\n AgentPassResult,\n AgentPassVerified,\n AgentPassUnverified,\n VerifierOptions,\n} from \"./types.js\";\n\n// ── App Router helper ────────────────────────────────────────────────────────\n\nimport type { NextRequest } from \"next/server\";\nimport type { AgentPassResult, AgentPassClaims } from \"./types.js\";\n\n/**\n * Extract the verified AgentPass identity from a Next.js App Router request.\n *\n * This function reads the headers set by `createAgentPassMiddleware`. It must\n * only be called from route handlers that sit behind the middleware.\n *\n * @example\n * ```ts\n * // app/api/data/route.ts\n * import { getAgentPassResult } from '@agent-id/nextjs';\n *\n * export async function GET(request: NextRequest) {\n * const agentPass = getAgentPassResult(request);\n * if (!agentPass.verified) {\n * return Response.json({ error: 'Unauthorized' }, { status: 403 });\n * }\n * return Response.json({ sub: agentPass.claims.sub });\n * }\n * ```\n */\nexport function getAgentPassResult(request: NextRequest): AgentPassResult {\n const verified = request.headers.get(\"x-agentpass-verified\");\n\n if (verified !== \"true\") {\n return {\n verified: false,\n reason: verified === \"false\" ? \"no_token\" : \"not_agent\",\n };\n }\n\n const claimsJson = request.headers.get(\"x-agentpass-claims\");\n if (!claimsJson) {\n return { verified: false, reason: \"invalid_token\" };\n }\n\n try {\n const claims = JSON.parse(claimsJson) as AgentPassClaims;\n return { verified: true, claims };\n } catch {\n return { verified: false, reason: \"invalid_token\" };\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/middleware.ts","../src/detect.ts","../src/verify.ts","../src/index.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport { isAgentRequest, extractToken } from \"./detect.js\";\nimport { verifyAgentIDToken } from \"./verify.js\";\nimport type { VerifierOptions, AgentIDClaims } from \"./types.js\";\n\nexport type AgentIDMiddlewareOptions = VerifierOptions & {\n /**\n * Optional callback invoked when an agent is detected but carries no\n * valid token. Return a `NextResponse` to override the default 403.\n */\n onUnauthorizedAgent?: (\n request: NextRequest,\n reason: string\n ) => NextResponse | void;\n};\n\n/**\n * Headers injected by this proxy into downstream route handlers.\n * They are stripped from the *incoming* request first to prevent spoofing.\n */\nconst MANAGED_HEADERS = [\n \"x-agentid-verified\",\n \"x-agentid-sub\",\n \"x-agentid-claims\",\n] as const;\n\n/**\n * Factory that returns a Next.js proxy function which detects AI-agent\n * traffic and enforces AgentID JWT authentication.\n *\n * @example\n * ```ts\n * // proxy.ts (project root)\n * import { createAgentIDMiddleware } from '@agent-id/nextjs';\n *\n * const agentID = createAgentIDMiddleware({\n * blockUnauthorizedAgents: true,\n * });\n *\n * export function proxy(request: NextRequest) {\n * return agentID(request);\n * }\n *\n * export const config = { matcher: '/api/:path*' };\n * ```\n */\nexport function createAgentIDMiddleware(\n options: AgentIDMiddlewareOptions = {}\n) {\n const {\n jwksUrl,\n blockUnauthorizedAgents = true,\n clockTolerance = 30,\n onUnauthorizedAgent,\n } = options;\n\n return async function agentIDMiddleware(\n request: NextRequest\n ): Promise<NextResponse> {\n // Clone incoming headers so we can safely mutate them.\n const requestHeaders = new Headers(request.headers);\n\n // ── Security: strip client-supplied AgentID headers ──────────────────\n // Without this, a malicious agent could send x-agentid-verified: true\n // and bypass the check in route handlers.\n for (const name of MANAGED_HEADERS) {\n requestHeaders.delete(name);\n }\n\n // ── Non-agent traffic ──────────────────────────────────────────────────\n if (!isAgentRequest(requestHeaders)) {\n return NextResponse.next({ request: { headers: requestHeaders } });\n }\n\n // ── Agent detected — require a valid JWT ───────────────────────────────\n const token = extractToken(requestHeaders);\n\n if (!token) {\n return unauthorized(\n request,\n requestHeaders,\n 'Agent request missing AgentID token. Provide \"Authorization: Bearer <token>\".',\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verify the JWT ─────────────────────────────────────────────────────\n let claims: AgentIDClaims;\n try {\n claims = await verifyAgentIDToken(token, {\n ...(jwksUrl !== undefined && { jwksUrl }),\n clockTolerance,\n });\n } catch (err) {\n // Never surface the raw error to the caller — it might leak internals.\n console.warn(\n \"[agent-id] Token verification failed:\",\n err instanceof Error ? err.message : String(err)\n );\n return unauthorized(\n request,\n requestHeaders,\n \"Invalid or expired AgentID token.\",\n blockUnauthorizedAgents,\n onUnauthorizedAgent\n );\n }\n\n // ── Verified — inject identity into request headers ────────────────────\n // Route handlers read these via getAgentIDResult(request).\n requestHeaders.set(\"x-agentid-verified\", \"true\");\n requestHeaders.set(\"x-agentid-sub\", claims.sub);\n // Full claims encoded as JSON for type-safe extraction by helpers.\n requestHeaders.set(\"x-agentid-claims\", JSON.stringify(claims));\n\n return NextResponse.next({ request: { headers: requestHeaders } });\n };\n}\n\n// ── Internal helper ──────────────────────────────────────────────────────────\n\nfunction unauthorized(\n request: NextRequest,\n requestHeaders: Headers,\n message: string,\n block: boolean,\n onUnauthorized: AgentIDMiddlewareOptions[\"onUnauthorizedAgent\"]\n): NextResponse {\n if (onUnauthorized) {\n const custom = onUnauthorized(request, message);\n if (custom) return custom;\n }\n\n if (block) {\n return NextResponse.json(\n { error: \"AGENT_UNAUTHORIZED\", message },\n { status: 403 }\n );\n }\n\n // Pass through with a \"not verified\" marker so route handlers can still\n // distinguish agent traffic from human traffic.\n requestHeaders.set(\"x-agentid-verified\", \"false\");\n return NextResponse.next({ request: { headers: requestHeaders } });\n}\n","/**\n * Bot / AI-agent detection for Next.js (Edge Runtime compatible).\n *\n * Uses layered heuristics:\n * 1. Explicit bot / AI-agent User-Agent strings\n * 2. Cloudflare Bot Management score (if header is present)\n * 3. Absence of browser signals (no Accept-Language + non-browser UA)\n *\n * The detector is intentionally conservative — when in doubt it lets the\n * request through (fail open). A false negative (undetected bot) means the\n * request passes without a JWT check. A false positive (human flagged as bot)\n * would block legitimate traffic, which is much worse.\n */\n\nconst BOT_UA_PATTERNS: RegExp[] = [\n // OpenAI\n /GPTBot/i,\n /ChatGPT-User/i,\n /OAI-SearchBot/i,\n // Anthropic / Claude\n /ClaudeBot/i,\n /Claude-Web/i,\n /anthropic-ai/i,\n /Claude-User/i,\n // Google\n /Googlebot/i,\n /Google-Extended/i,\n /AdsBot-Google/i,\n // Microsoft / Bing\n /bingbot/i,\n /msnbot/i,\n // AI search engines\n /PerplexityBot/i,\n /YouBot/i,\n // Common HTTP automation libraries\n /python-requests/i,\n /node-fetch/i,\n /\\baxios\\b/i,\n /\\bgot\\b\\//i,\n /\\bundici\\b/i,\n /\\bcurl\\b/i,\n /\\bwget\\b/i,\n /\\bhttpie\\b/i,\n // Generic crawler signals (word-boundary matched to reduce false positives)\n /\\bbot\\b/i,\n /\\bcrawler\\b/i,\n /\\bspider\\b/i,\n /\\bscraper\\b/i,\n /\\bfetcher\\b/i,\n // MCP / AgentID clients\n /mcp-client/i,\n /agentid-client/i,\n];\n\n// Every major browser includes \"Mozilla/5.0\" — its absence is a strong signal\nconst BROWSER_UA_RE = /Mozilla\\/5\\.0/i;\n\n/**\n * Returns `true` if the request appears to originate from an automated\n * agent or bot rather than a human browser.\n *\n * @param headers - The `Headers` object from a Next.js `NextRequest`\n */\nexport function isAgentRequest(headers: Headers): boolean {\n const ua = headers.get(\"user-agent\") ?? \"\";\n\n // No User-Agent → definitely automated\n if (!ua) return true;\n\n // Explicit bot / agent UA match\n if (BOT_UA_PATTERNS.some((p) => p.test(ua))) return true;\n\n // Cloudflare Bot Management: the `cf-bot-management` header contains a\n // JSON blob with a `score` field (0–100). Score < 30 → highly likely bot.\n const cfRaw = headers.get(\"cf-bot-management\");\n if (cfRaw) {\n try {\n const parsed = JSON.parse(cfRaw) as { score?: unknown };\n if (typeof parsed.score === \"number\" && parsed.score < 30) return true;\n } catch {\n // Malformed header — fail open (pass through)\n }\n }\n\n // Heuristic: non-browser UA + no Accept-Language → likely automated\n if (!BROWSER_UA_RE.test(ua) && !headers.get(\"accept-language\")) return true;\n\n return false;\n}\n\n/**\n * Extract the AgentID JWT from request headers.\n *\n * Checks in order:\n * 1. `Authorization: Bearer <token>` (preferred)\n * 2. `X-AgentID-Token` (fallback when Authorization is stripped by proxies)\n *\n * @returns The raw JWT string, or `null` if absent.\n */\nexport function extractToken(headers: Headers): string | null {\n // Primary: standard Authorization header\n const auth = headers.get(\"authorization\") ?? \"\";\n if (auth.startsWith(\"Bearer \")) {\n const token = auth.slice(7).trim();\n if (token) return token;\n }\n\n // Fallback: custom header\n const custom = headers.get(\"x-agentid-token\")?.trim();\n if (custom) return custom;\n\n return null;\n}\n","import { createRemoteJWKSet, jwtVerify } from \"jose\";\nimport type { AgentIDClaims } from \"./types.js\";\n\nconst DEFAULT_JWKS_URL = \"https://agentpass.vercel.app/api/jwks\";\n\n/**\n * Module-level JWKS cache — one RemoteJWKSet instance per unique URL.\n * jose caches the fetched key material internally; re-fetches when the\n * cache TTL (1 h) expires or a new `kid` is seen.\n */\nconst jwksSets = new Map<string, ReturnType<typeof createRemoteJWKSet>>();\n\nfunction getJwks(rawUrl: string): ReturnType<typeof createRemoteJWKSet> {\n if (!jwksSets.has(rawUrl)) {\n const url = new URL(rawUrl); // throws on malformed URL\n\n // Security: HTTPS is mandatory to prevent MITM on the public-key fetch.\n // Localhost is whitelisted for local development / CI.\n const isLocal =\n url.hostname === \"localhost\" ||\n url.hostname === \"127.0.0.1\" ||\n url.hostname === \"::1\";\n\n if (url.protocol !== \"https:\" && !isLocal) {\n throw new Error(\n `[agent-id] jwksUrl must use HTTPS, received: ${rawUrl}`\n );\n }\n\n jwksSets.set(\n rawUrl,\n createRemoteJWKSet(url, {\n cacheMaxAge: 60 * 60 * 1_000, // 1 hour in ms\n })\n );\n }\n\n return jwksSets.get(rawUrl)!;\n}\n\nexport interface VerifyTokenOptions {\n jwksUrl?: string;\n clockTolerance?: number;\n}\n\n/**\n * Verify an AgentID JWT and return its decoded claims.\n *\n * Security guarantees\n * ───────────────────\n * • Signature — RS256, verified against the live JWKS public key.\n * • Algorithm — strict allowlist `['RS256']`; alg:none and HS256 are\n * rejected before signature verification even begins.\n * • Issuer — must be exactly \"agentid\".\n * • Expiry — enforced; configurable clock tolerance (default 30 s).\n * • kid — jose matches the JWT `kid` header to the JWKS automatically.\n * • auth_method — validated at runtime; must equal \"bankid\".\n *\n * @throws if the token is invalid, expired, or fails any check.\n */\nexport async function verifyAgentIDToken(\n token: string,\n options: VerifyTokenOptions = {}\n): Promise<AgentIDClaims> {\n const jwksUrl = options.jwksUrl ?? DEFAULT_JWKS_URL;\n const JWKS = getJwks(jwksUrl);\n\n const { payload } = await jwtVerify(token, JWKS, {\n issuer: \"agentid\",\n // ↓ Critical — explicit allowlist prevents algorithm-confusion attacks.\n // Any token claiming alg:\"none\", alg:\"HS256\", or anything else is\n // rejected before signature verification.\n algorithms: [\"RS256\"],\n clockTolerance: options.clockTolerance ?? 30,\n });\n\n // Runtime validation of AgentID-specific claims.\n if (payload.auth_method !== \"bankid\") {\n throw new Error(\n `[agent-id] JWT has invalid auth_method: expected \"bankid\", got \"${\n payload.auth_method ?? \"undefined\"\n }\"`\n );\n }\n if (typeof payload.sub !== \"string\" || payload.sub.length === 0) {\n throw new Error(\"[agent-id] JWT is missing the sub claim\");\n }\n\n return payload as unknown as AgentIDClaims;\n}\n","export { createAgentIDMiddleware } from \"./middleware.js\";\nexport type { AgentIDMiddlewareOptions } from \"./middleware.js\";\n\nexport { verifyAgentIDToken } from \"./verify.js\";\nexport type { VerifyTokenOptions } from \"./verify.js\";\n\nexport { isAgentRequest, extractToken } from \"./detect.js\";\n\nexport type {\n AgentIDClaims,\n AgentIDResult,\n AgentIDVerified,\n AgentIDUnverified,\n VerifierOptions,\n} from \"./types.js\";\n\n// ── App Router helper ────────────────────────────────────────────────────────\n\nimport type { NextRequest } from \"next/server\";\nimport type { AgentIDResult, AgentIDClaims } from \"./types.js\";\n\n/**\n * Extract the verified AgentID identity from a Next.js App Router request.\n *\n * This function reads the headers set by `createAgentIDMiddleware`. It must\n * only be called from route handlers that sit behind the proxy.\n *\n * @example\n * ```ts\n * // app/api/data/route.ts\n * import { getAgentIDResult } from '@agent-id/nextjs';\n *\n * export async function GET(request: NextRequest) {\n * const agent = getAgentIDResult(request);\n * if (!agent.verified) {\n * return Response.json({ error: 'Unauthorized' }, { status: 403 });\n * }\n * return Response.json({ sub: agent.claims.sub });\n * }\n * ```\n */\nexport function getAgentIDResult(request: NextRequest): AgentIDResult {\n const verified = request.headers.get(\"x-agentid-verified\");\n\n if (verified !== \"true\") {\n return {\n verified: false,\n reason: verified === \"false\" ? \"no_token\" : \"not_agent\",\n };\n }\n\n const claimsJson = request.headers.get(\"x-agentid-claims\");\n if (!claimsJson) {\n return { verified: false, reason: \"invalid_token\" };\n }\n\n try {\n const claims = JSON.parse(claimsJson) as AgentIDClaims;\n return { verified: true, claims };\n } catch {\n return { verified: false, reason: \"invalid_token\" };\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-id/nextjs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Agent-ID verifier middleware for Next.js — blocks unauthorized AI agents from your API routes",
|
|
5
5
|
"keywords": ["agent-id", "agentpass", "bankid", "jwt", "nextjs", "middleware", "ai-agent", "mcp", "bot-detection"],
|
|
6
6
|
"license": "MIT",
|