@flink-app/jwt-auth-plugin 2.0.0-alpha.97 → 2.0.0-alpha.98

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/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @flink-app/jwt-auth-plugin
2
2
 
3
+ ## 2.0.0-alpha.98
4
+
5
+ ### Minor Changes
6
+
7
+ - 2f4a132: feat: expose verified token on the request
8
+
9
+ The JWT auth plugin now populates `req.token` (the decoded, signature-verified
10
+ payload) and `req.rawToken` (the original token string) after successful
11
+ authentication. Handlers can read token claims such as `jti` directly instead of
12
+ re-extracting and re-decoding the bearer token with an unverified decode.
13
+
14
+ ```ts
15
+ // Before
16
+ const authHeader = req.headers.authorization;
17
+ const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : undefined;
18
+ const decoded = bearer ? (jsonwebtoken.decode(bearer) as { jti?: string }) : null;
19
+ const sessionId = decoded?.jti ?? legacySessionId(req.user._id);
20
+
21
+ // After
22
+ const { jti } = (req.token as { jti?: string }) ?? {};
23
+ const sessionId = jti ?? legacySessionId(req.user._id);
24
+ ```
25
+
26
+ `token` / `rawToken` are added as optional fields on the core `FlinkRequest`
27
+ type, populated by auth plugins that have a token concept.
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [2f4a132]
32
+ - @flink-app/flink@2.0.0-alpha.98
33
+
3
34
  ## 2.0.0-alpha.97
4
35
 
5
36
  ### Minor Changes
@@ -148,6 +148,10 @@ function authenticateRequest(req_1, routePermissions_1, rolePermissions_1, ctx_1
148
148
  _c.label = 3;
149
149
  case 3:
150
150
  req.user = user;
151
+ // Expose the verified token so handlers can read claims (e.g. jti)
152
+ // without re-extracting and re-decoding (and without an unverified decode).
153
+ req.token = decodedToken;
154
+ req.rawToken = token;
151
155
  // Populate userPermissions from resolved permissions
152
156
  req.userPermissions = computeUserPermissions(user, decodedToken, {
153
157
  useDynamicRoles: useDynamicRoles,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/jwt-auth-plugin",
3
- "version": "2.0.0-alpha.97",
3
+ "version": "2.0.0-alpha.98",
4
4
  "description": "Flink plugin for JWT auth",
5
5
  "author": "joel@frost.se",
6
6
  "license": "MIT",
@@ -19,10 +19,10 @@
19
19
  "@types/node": "22.13.10",
20
20
  "ts-node": "^10.9.2",
21
21
  "tsc-watch": "^4.2.9",
22
- "@flink-app/flink": "2.0.0-alpha.97"
22
+ "@flink-app/flink": "2.0.0-alpha.98"
23
23
  },
24
24
  "peerDependencies": {
25
- "@flink-app/flink": ">=2.0.0-alpha.97"
25
+ "@flink-app/flink": ">=2.0.0-alpha.98"
26
26
  },
27
27
  "gitHead": "4243e3b3cd6d4e1ca001a61baa8436bf2bbe4113",
28
28
  "scripts": {
package/readme.md CHANGED
@@ -481,6 +481,35 @@ jwtAuthPlugin({
481
481
 
482
482
  **Solution**: Clear permission cache or reduce cache TTL
483
483
 
484
+ ## `req.token` & `req.rawToken` - Accessing Token Claims
485
+
486
+ After successful authentication the plugin attaches the verified token to the request:
487
+
488
+ - **`req.token`** - the decoded, **signature-verified** token payload (claims). Same object passed to `getUser`.
489
+ - **`req.rawToken`** - the original token string that authenticated the request (Bearer header or custom `tokenExtractor` output).
490
+
491
+ This lets handlers read token claims (e.g. `jti`, custom session ids) without re-extracting and re-decoding the bearer token themselves - and without an unverified decode.
492
+
493
+ ```typescript
494
+ const handler: Handler<Ctx, any, any> = async ({ req }) => {
495
+ // Read claims straight off the verified token
496
+ const { jti } = (req.token as { jti?: string }) ?? {};
497
+ const sessionId = jti ?? legacySessionId(req.user._id);
498
+
499
+ // Forward the raw token to a downstream service if needed
500
+ await downstream.call({ headers: { authorization: `Bearer ${req.rawToken}` } });
501
+
502
+ return { data: { sessionId } };
503
+ };
504
+ ```
505
+
506
+ **Notes:**
507
+
508
+ - Both fields are only set once authentication **succeeds** (a rejected request never reaches the handler).
509
+ - `req.token` is the exact decoded payload, including framework-added claims like `iat` and `exp`.
510
+ - Signature is verified during decode; unlike a bare `jsonwebtoken.decode(...)`, you don't need to (and shouldn't) re-verify.
511
+ - `req.rawToken` reflects whatever `tokenExtractor` returned, so it works for cookie/query-based tokens too.
512
+
484
513
  ## `req.userPermissions` - AI Permissions Integration
485
514
 
486
515
  Starting in **v1.1.0**, the JWT auth plugin automatically populates `req.userPermissions` with the resolved permissions array. This field is specifically designed for use with Flink's AI agents and tools system, providing a standardized way to handle permissions across handlers, agents, and tools.
@@ -102,6 +102,62 @@ describe("FlinkJwtAuthPlugin", () => {
102
102
  expect(authenticated).toBeTruthy();
103
103
  });
104
104
 
105
+ it("should expose decoded token on req.token and raw string on req.rawToken", async () => {
106
+ const secret = "secret";
107
+ const userId = "123";
108
+ const encodedToken = jwtSimple.encode(
109
+ { id: userId, jti: "session-abc", roles: ["user"] },
110
+ secret
111
+ );
112
+
113
+ const plugin = jwtAuthPlugin({
114
+ secret,
115
+ getUser: async ({ id }: { id: string }, req) => ({ id, username: "username" }),
116
+ rolePermissions: {
117
+ user: ["*"],
118
+ },
119
+ });
120
+
121
+ const mockRequest = {
122
+ headers: {
123
+ authorization: "Bearer " + encodedToken,
124
+ },
125
+ } as FlinkRequest;
126
+
127
+ const authenticated = await plugin.authenticateRequest(mockRequest, "foo");
128
+
129
+ expect(authenticated).toBeTruthy();
130
+ expect(mockRequest.rawToken).toBe(encodedToken);
131
+ expect(mockRequest.token?.jti).toBe("session-abc");
132
+ expect(mockRequest.token?.id).toBe(userId);
133
+ });
134
+
135
+ it("should set req.rawToken to the tokenExtractor output, not just Bearer", async () => {
136
+ const secret = "secret";
137
+ const customToken = jwtSimple.encode(
138
+ { id: "1", jti: "from-query", roles: ["user"] },
139
+ secret
140
+ );
141
+
142
+ const plugin = jwtAuthPlugin({
143
+ secret,
144
+ getUser: async ({ id }: { id: string }, req) => ({ id, username: "username" }),
145
+ rolePermissions: { user: ["*"] },
146
+ tokenExtractor: (req) => req.query?.token as string,
147
+ });
148
+
149
+ const mockRequest = {
150
+ headers: {},
151
+ query: { token: customToken },
152
+ } as unknown as FlinkRequest;
153
+
154
+ const authenticated = await plugin.authenticateRequest(mockRequest, "foo");
155
+
156
+ expect(authenticated).toBeTruthy();
157
+ expect(mockRequest.rawToken).toBe(customToken);
158
+ expect(mockRequest.token?.jti).toBe("from-query");
159
+ });
160
+
105
161
  it("should generate token", async () => {
106
162
  const secret = "secret";
107
163
  const plugin = jwtAuthPlugin({
@@ -250,6 +250,11 @@ async function authenticateRequest(
250
250
 
251
251
  req.user = user;
252
252
 
253
+ // Expose the verified token so handlers can read claims (e.g. jti)
254
+ // without re-extracting and re-decoding (and without an unverified decode).
255
+ req.token = decodedToken;
256
+ req.rawToken = token;
257
+
253
258
  // Populate userPermissions from resolved permissions
254
259
  req.userPermissions = computeUserPermissions(user, decodedToken, {
255
260
  useDynamicRoles,