@maximemrf/adonisjs-jwt 0.7.0-alpha.1 → 0.7.0

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 CHANGED
@@ -116,6 +116,27 @@ useCookies: true
116
116
 
117
117
  If you just want to use jwt with the bearer token no need to set `useCookies` to `false` you can just remove it.
118
118
 
119
+ ## JWKS
120
+
121
+ You can use JWKS to verify the token by setting the `jwks` option in the guard configuration.
122
+
123
+ ```typescript
124
+ // ...
125
+ jwt: jwtGuard({
126
+ // ...
127
+ jwks: {
128
+ jwksUri: 'https://your-auth-server/.well-known/jwks.json',
129
+ // you can pass any options accepted by jwks-rsa package
130
+ cache: true,
131
+ rateLimit: true,
132
+ },
133
+ }),
134
+ // ...
135
+ ```
136
+
137
+ > [!WARNING]
138
+ > If you enable JWKS, you cannot use the `auth.use('jwt').generate(user)` and `auth.use('jwt').generateWithRefreshToken()` method because the token is signed by an external provider. You can only use the `authenticate` (or `check` / `getUserOrFail`) method to verify the token.
139
+
119
140
  ## Refresh Tokens
120
141
 
121
142
  To use refresh tokens, you have to set the `refreshTokenUserProvider` in the guard configuration, see the example above.
@@ -199,16 +220,18 @@ router.post('login', async ({ request, auth }) => {
199
220
  })
200
221
 
201
222
  // if the jwt guard is the default guard
202
- router.get('/', async ({ auth }) => {
203
- return auth.getUserOrFail()
204
- })
205
- .use(middleware.auth())
223
+ router
224
+ .get('/', async ({ auth }) => {
225
+ return auth.getUserOrFail()
226
+ })
227
+ .use(middleware.auth())
206
228
 
207
229
  // if the jwt guard is not the default guard
208
- router.get('/', async ({ auth }) => {
209
- return auth.use('jwt').getUserOrFail()
210
- })
211
- .use(middleware.auth({ guards: ['jwt'] }))
230
+ router
231
+ .get('/', async ({ auth }) => {
232
+ return auth.use('jwt').getUserOrFail()
233
+ })
234
+ .use(middleware.auth({ guards: ['jwt'] }))
212
235
 
213
236
  // if you use the refresh token
214
237
  router.post('jwt/refresh', async ({ auth }) => {
@@ -4,6 +4,7 @@ import { JwtGuardUser, JwtUserProviderContract } from './types.js';
4
4
  import { JwtGuard } from './jwt.js';
5
5
  import type { StringValue } from 'ms';
6
6
  import { AccessTokensUserProviderContract } from '@adonisjs/auth/types/access_tokens';
7
+ import { Options } from 'jwks-rsa';
7
8
  export declare function jwtGuard<UserProvider extends JwtUserProviderContract<unknown>>(config: {
8
9
  provider: UserProvider;
9
10
  refreshTokenUserProvider?: AccessTokensUserProviderContract<unknown>;
@@ -15,4 +16,5 @@ export declare function jwtGuard<UserProvider extends JwtUserProviderContract<un
15
16
  refreshTokenAbilities?: string[];
16
17
  secret?: string;
17
18
  content: <T>(user: JwtGuardUser<T>) => Record<string | number, any>;
19
+ jwks?: Options;
18
20
  }): GuardConfigProvider<(ctx: HttpContext) => JwtGuard<UserProvider>>;
@@ -13,6 +13,7 @@ export function jwtGuard(config) {
13
13
  useCookiesForRefreshToken: config.useCookiesForRefreshToken,
14
14
  refreshTokenAbilities: config.refreshTokenAbilities,
15
15
  content: config.content,
16
+ jwks: config.jwks,
16
17
  };
17
18
  return (ctx) => new JwtGuard(ctx, config.provider, options);
18
19
  },
@@ -0,0 +1,7 @@
1
+ import { Options, JwksClient } from 'jwks-rsa';
2
+ export declare class JwksManager {
3
+ private client;
4
+ constructor(options: Options);
5
+ getClient(): JwksClient;
6
+ getSigningKey(kid: string): Promise<string>;
7
+ }
@@ -0,0 +1,14 @@
1
+ import { JwksClient } from 'jwks-rsa';
2
+ export class JwksManager {
3
+ client;
4
+ constructor(options) {
5
+ this.client = new JwksClient(options);
6
+ }
7
+ getClient() {
8
+ return this.client;
9
+ }
10
+ async getSigningKey(kid) {
11
+ const key = await this.client.getSigningKey(kid);
12
+ return key.getPublicKey();
13
+ }
14
+ }
package/build/src/jwt.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { errors } from '@adonisjs/auth';
2
2
  import jwt from 'jsonwebtoken';
3
3
  import { Secret } from '@adonisjs/core/helpers';
4
+ import { JwksManager } from './jwks.js';
4
5
  export class JwtGuard {
5
6
  #ctx;
6
7
  #userProvider;
@@ -8,6 +9,7 @@ export class JwtGuard {
8
9
  #options;
9
10
  #tokenName;
10
11
  #refreshTokenName = 'refreshToken';
12
+ #jwksManager;
11
13
  constructor(ctx, userProvider, option) {
12
14
  this.#ctx = ctx;
13
15
  this.#userProvider = userProvider;
@@ -16,6 +18,7 @@ export class JwtGuard {
16
18
  if (!this.#options.content)
17
19
  this.#options.content = (user) => ({ userId: user.getId() });
18
20
  this.#tokenName = this.#options.tokenName ?? 'token';
21
+ this.#jwksManager = this.#options.jwks ? new JwksManager(this.#options.jwks) : undefined;
19
22
  }
20
23
  /**
21
24
  * A unique name for the guard driver
@@ -39,6 +42,11 @@ export class JwtGuard {
39
42
  * Generate a JWT token for a given user.
40
43
  */
41
44
  async generate(user) {
45
+ if (this.#options.jwks) {
46
+ throw new errors.E_UNAUTHORIZED_ACCESS("You can't use the auth.generate method with jwks", {
47
+ guardDriverName: this.driverName,
48
+ });
49
+ }
42
50
  const providerUser = await this.#userProvider.createUserForGuard(user);
43
51
  const token = jwt.sign(this.#options.content(providerUser), this.#options.secret, this.#options.expiresIn
44
52
  ? {
@@ -126,14 +134,26 @@ export class JwtGuard {
126
134
  */
127
135
  let payload;
128
136
  try {
129
- payload = jwt.verify(token, this.#options.secret);
137
+ if (this.#jwksManager) {
138
+ const decoded = jwt.decode(token, { complete: true });
139
+ if (!decoded || !decoded.header || !decoded.header.kid) {
140
+ throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
141
+ guardDriverName: this.driverName,
142
+ });
143
+ }
144
+ const key = await this.#jwksManager.getSigningKey(decoded.header.kid);
145
+ payload = jwt.verify(token, key);
146
+ }
147
+ else {
148
+ payload = jwt.verify(token, this.#options.secret);
149
+ }
130
150
  }
131
- catch (error) {
151
+ catch {
132
152
  throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
133
153
  guardDriverName: this.driverName,
134
154
  });
135
155
  }
136
- if (typeof payload !== 'object' || !('userId' in payload)) {
156
+ if (!payload || typeof payload !== 'object' || !('userId' in payload)) {
137
157
  throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
138
158
  guardDriverName: this.driverName,
139
159
  });
@@ -154,6 +174,11 @@ export class JwtGuard {
154
174
  }
155
175
  async generateWithRefreshToken(refreshToken) {
156
176
  this.authenticationAttempted = true;
177
+ if (this.#options.jwks) {
178
+ throw new errors.E_UNAUTHORIZED_ACCESS('JWKS is not supported for refresh token', {
179
+ guardDriverName: this.driverName,
180
+ });
181
+ }
157
182
  if (!this.#refreshTokenUserProvider) {
158
183
  throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
159
184
  guardDriverName: this.driverName,
@@ -264,7 +289,7 @@ export class JwtGuard {
264
289
  const token = await this.generate(user);
265
290
  return {
266
291
  headers: {
267
- authorization: `Bearer ${this.#options.useCookies ? token : token.token}`,
292
+ authorization: `Bearer ${token.token}`,
268
293
  },
269
294
  };
270
295
  }
@@ -1,6 +1,7 @@
1
1
  import { symbols } from '@adonisjs/auth';
2
2
  import type { StringValue } from 'ms';
3
3
  import { AccessTokensUserProviderContract } from '@adonisjs/auth/types/access_tokens';
4
+ import { Options } from 'jwks-rsa';
4
5
  /**
5
6
  * The bridge between the User provider and the
6
7
  * Guard
@@ -40,6 +41,7 @@ export type BaseJwtContent = {
40
41
  };
41
42
  export type JwtGuardOptions<RealUser extends any = unknown> = {
42
43
  secret: string;
44
+ jwks?: Options;
43
45
  refreshTokenUserProvider?: AccessTokensUserProviderContract<RealUser>;
44
46
  tokenName?: string;
45
47
  expiresIn?: number | StringValue;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maximemrf/adonisjs-jwt",
3
3
  "description": "Adonisjs v6 package for authentication with jwt token",
4
- "version": "0.7.0-alpha.1",
4
+ "version": "0.7.0",
5
5
  "engines": {
6
6
  "node": ">=20.6.0"
7
7
  },
@@ -44,15 +44,17 @@
44
44
  "@japa/assert": "^4.2.0",
45
45
  "@japa/file-system": "^3.0.0",
46
46
  "@japa/runner": "^5.3.0",
47
- "@swc/core": "^1.15.8",
47
+ "@types/nock": "^10.0.3",
48
+ "@swc/core": "^1.15.10",
48
49
  "@types/node": "^22.15.18",
49
50
  "c8": "^10.1.3",
50
51
  "copyfiles": "^2.4.1",
51
52
  "del-cli": "^7.0.0",
52
53
  "eslint": "^9.39.2",
53
54
  "luxon": "^3.7.2",
54
- "np": "^10.3.0",
55
- "prettier": "^3.8.0",
55
+ "nock": "^14.0.10",
56
+ "np": "^11.0.1",
57
+ "prettier": "^3.8.1",
56
58
  "sqlite3": "^5.1.7",
57
59
  "timekeeper": "^2.3.1",
58
60
  "ts-node-maintained": "^10.9.6",
@@ -61,6 +63,7 @@
61
63
  "dependencies": {
62
64
  "@types/jsonwebtoken": "^9.0.10",
63
65
  "jsonwebtoken": "^9.0.3",
66
+ "jwks-rsa": "^3.2.2",
64
67
  "ms": "^2.1.3"
65
68
  },
66
69
  "peerDependencies": {