@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 +31 -8
- package/build/src/define_config.d.ts +2 -0
- package/build/src/define_config.js +1 -0
- package/build/src/jwks.d.ts +7 -0
- package/build/src/jwks.js +14 -0
- package/build/src/jwt.js +29 -4
- package/build/src/types.d.ts +2 -0
- package/package.json +7 -4
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
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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,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
|
-
|
|
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
|
|
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 ${
|
|
292
|
+
authorization: `Bearer ${token.token}`,
|
|
268
293
|
},
|
|
269
294
|
};
|
|
270
295
|
}
|
package/build/src/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
"@
|
|
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
|
-
"
|
|
55
|
-
"
|
|
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": {
|