@maximemrf/adonisjs-jwt 0.7.0-alpha.2 → 0.7.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 +39 -0
- package/build/src/define_config.d.ts +2 -1
- package/build/src/define_config.js +1 -2
- package/build/src/jwt.js +26 -14
- package/build/src/types.d.ts +3 -2
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -79,6 +79,11 @@ const authConfig = defineConfig({
|
|
|
79
79
|
refreshTokenExpiresIn: '7d',
|
|
80
80
|
// ability to separate cookie usage for refresh token
|
|
81
81
|
useCookiesForRefreshToken: true,
|
|
82
|
+
// ability to configure the cookies options
|
|
83
|
+
cookie: {
|
|
84
|
+
httpOnly: true,
|
|
85
|
+
secure: true,
|
|
86
|
+
},
|
|
82
87
|
// limit the abilities of the refresh token
|
|
83
88
|
refreshTokenAbilities: ['refresh_token'],
|
|
84
89
|
// content is a function that takes the user and returns the content of the token, it can be optional, by default it returns only the user id
|
|
@@ -116,6 +121,40 @@ useCookies: true
|
|
|
116
121
|
|
|
117
122
|
If you just want to use jwt with the bearer token no need to set `useCookies` to `false` you can just remove it.
|
|
118
123
|
|
|
124
|
+
You can also pass options to the cookies. Note that `maxAge` and `expires` are omitted from the options because they are automatically handled by the package based on the token validity (`tokenExpiresIn` and `refreshTokenExpiresIn`).
|
|
125
|
+
By default, `httpOnly: true` and `secure: true` are enforced for better security, but you can override them using the `cookie` object configuration.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
cookie: {
|
|
129
|
+
httpOnly: true, // Default
|
|
130
|
+
secure: true, // Default
|
|
131
|
+
path: '/',
|
|
132
|
+
sameSite: 'lax',
|
|
133
|
+
// maxAge and expires cannot be set here
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## JWKS
|
|
138
|
+
|
|
139
|
+
You can use JWKS to verify the token by setting the `jwks` option in the guard configuration.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// ...
|
|
143
|
+
jwt: jwtGuard({
|
|
144
|
+
// ...
|
|
145
|
+
jwks: {
|
|
146
|
+
jwksUri: 'https://your-auth-server/.well-known/jwks.json',
|
|
147
|
+
// you can pass any options accepted by jwks-rsa package
|
|
148
|
+
cache: true,
|
|
149
|
+
rateLimit: true,
|
|
150
|
+
},
|
|
151
|
+
}),
|
|
152
|
+
// ...
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
> [!WARNING]
|
|
156
|
+
> 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.
|
|
157
|
+
|
|
119
158
|
## Refresh Tokens
|
|
120
159
|
|
|
121
160
|
To use refresh tokens, you have to set the `refreshTokenUserProvider` in the guard configuration, see the example above.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GuardConfigProvider } from '@adonisjs/auth/types';
|
|
2
2
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
|
-
import { JwtGuardUser, JwtUserProviderContract } from './types.js';
|
|
3
|
+
import { JwtGuardUser, JwtUserProviderContract, JwtCookieOptions } 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';
|
|
@@ -17,4 +17,5 @@ export declare function jwtGuard<UserProvider extends JwtUserProviderContract<un
|
|
|
17
17
|
secret?: string;
|
|
18
18
|
content: <T>(user: JwtGuardUser<T>) => Record<string | number, any>;
|
|
19
19
|
jwks?: Options;
|
|
20
|
+
cookie?: JwtCookieOptions;
|
|
20
21
|
}): GuardConfigProvider<(ctx: HttpContext) => JwtGuard<UserProvider>>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { JwtGuard } from './jwt.js';
|
|
2
|
-
import { JwksManager } from './jwks.js';
|
|
3
2
|
export function jwtGuard(config) {
|
|
4
3
|
return {
|
|
5
4
|
async resolver(_, app) {
|
|
@@ -15,7 +14,7 @@ export function jwtGuard(config) {
|
|
|
15
14
|
refreshTokenAbilities: config.refreshTokenAbilities,
|
|
16
15
|
content: config.content,
|
|
17
16
|
jwks: config.jwks,
|
|
18
|
-
|
|
17
|
+
cookie: config.cookie,
|
|
19
18
|
};
|
|
20
19
|
return (ctx) => new JwtGuard(ctx, config.provider, options);
|
|
21
20
|
},
|
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;
|
|
@@ -9,6 +10,7 @@ export class JwtGuard {
|
|
|
9
10
|
#tokenName;
|
|
10
11
|
#refreshTokenName = 'refreshToken';
|
|
11
12
|
#jwksManager;
|
|
13
|
+
#cookieOptions;
|
|
12
14
|
constructor(ctx, userProvider, option) {
|
|
13
15
|
this.#ctx = ctx;
|
|
14
16
|
this.#userProvider = userProvider;
|
|
@@ -17,7 +19,12 @@ export class JwtGuard {
|
|
|
17
19
|
if (!this.#options.content)
|
|
18
20
|
this.#options.content = (user) => ({ userId: user.getId() });
|
|
19
21
|
this.#tokenName = this.#options.tokenName ?? 'token';
|
|
20
|
-
this.#jwksManager = this.#options.
|
|
22
|
+
this.#jwksManager = this.#options.jwks ? new JwksManager(this.#options.jwks) : undefined;
|
|
23
|
+
this.#cookieOptions = {
|
|
24
|
+
httpOnly: true,
|
|
25
|
+
secure: true,
|
|
26
|
+
...this.#options.cookie,
|
|
27
|
+
};
|
|
21
28
|
}
|
|
22
29
|
/**
|
|
23
30
|
* A unique name for the guard driver
|
|
@@ -41,6 +48,11 @@ export class JwtGuard {
|
|
|
41
48
|
* Generate a JWT token for a given user.
|
|
42
49
|
*/
|
|
43
50
|
async generate(user) {
|
|
51
|
+
if (this.#options.jwks) {
|
|
52
|
+
throw new errors.E_UNAUTHORIZED_ACCESS("You can't use the auth.generate method with jwks", {
|
|
53
|
+
guardDriverName: this.driverName,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
44
56
|
const providerUser = await this.#userProvider.createUserForGuard(user);
|
|
45
57
|
const token = jwt.sign(this.#options.content(providerUser), this.#options.secret, this.#options.expiresIn
|
|
46
58
|
? {
|
|
@@ -58,12 +70,14 @@ export class JwtGuard {
|
|
|
58
70
|
}
|
|
59
71
|
if (this.#options.useCookies) {
|
|
60
72
|
this.#ctx.response.cookie(`${this.#tokenName}`, token, {
|
|
61
|
-
|
|
73
|
+
...this.#cookieOptions,
|
|
74
|
+
maxAge: this.#options.expiresIn,
|
|
62
75
|
});
|
|
63
76
|
}
|
|
64
77
|
if (this.#options.useCookiesForRefreshToken && refreshToken) {
|
|
65
78
|
this.#ctx.response.cookie(`${this.#refreshTokenName}`, refreshToken, {
|
|
66
|
-
|
|
79
|
+
...this.#cookieOptions,
|
|
80
|
+
maxAge: this.#options.refreshTokenExpiresIn,
|
|
67
81
|
});
|
|
68
82
|
}
|
|
69
83
|
return {
|
|
@@ -88,17 +102,10 @@ export class JwtGuard {
|
|
|
88
102
|
return this.getUserOrFail();
|
|
89
103
|
}
|
|
90
104
|
this.authenticationAttempted = true;
|
|
91
|
-
const cookieHeader = this.#ctx.request.request.headers.cookie;
|
|
92
|
-
let token;
|
|
93
105
|
/**
|
|
94
|
-
*
|
|
106
|
+
* Try to read the token from the cookies.
|
|
95
107
|
*/
|
|
96
|
-
|
|
97
|
-
const regex = new RegExp(`${this.#tokenName}=([^;]*)`);
|
|
98
|
-
token =
|
|
99
|
-
this.#ctx.request.cookie(`${this.#tokenName}`) ??
|
|
100
|
-
(this.#ctx.request.request.headers.cookie.match(regex) || [])[1];
|
|
101
|
-
}
|
|
108
|
+
let token = this.#ctx.request.cookie(`${this.#tokenName}`);
|
|
102
109
|
/**
|
|
103
110
|
* If token is missing on cookies, then try to read it from the header authorization
|
|
104
111
|
*/
|
|
@@ -142,7 +149,7 @@ export class JwtGuard {
|
|
|
142
149
|
payload = jwt.verify(token, this.#options.secret);
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
|
-
catch
|
|
152
|
+
catch {
|
|
146
153
|
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
|
|
147
154
|
guardDriverName: this.driverName,
|
|
148
155
|
});
|
|
@@ -168,6 +175,11 @@ export class JwtGuard {
|
|
|
168
175
|
}
|
|
169
176
|
async generateWithRefreshToken(refreshToken) {
|
|
170
177
|
this.authenticationAttempted = true;
|
|
178
|
+
if (this.#options.jwks) {
|
|
179
|
+
throw new errors.E_UNAUTHORIZED_ACCESS('JWKS is not supported for refresh token', {
|
|
180
|
+
guardDriverName: this.driverName,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
171
183
|
if (!this.#refreshTokenUserProvider) {
|
|
172
184
|
throw new errors.E_UNAUTHORIZED_ACCESS('Unauthorized access', {
|
|
173
185
|
guardDriverName: this.driverName,
|
|
@@ -278,7 +290,7 @@ export class JwtGuard {
|
|
|
278
290
|
const token = await this.generate(user);
|
|
279
291
|
return {
|
|
280
292
|
headers: {
|
|
281
|
-
authorization: `Bearer ${
|
|
293
|
+
authorization: `Bearer ${token.token}`,
|
|
282
294
|
},
|
|
283
295
|
};
|
|
284
296
|
}
|
package/build/src/types.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { symbols } from '@adonisjs/auth';
|
|
|
2
2
|
import type { StringValue } from 'ms';
|
|
3
3
|
import { AccessTokensUserProviderContract } from '@adonisjs/auth/types/access_tokens';
|
|
4
4
|
import { Options } from 'jwks-rsa';
|
|
5
|
-
import {
|
|
5
|
+
import type { CookieOptions } from '@adonisjs/core/types/http';
|
|
6
|
+
export type JwtCookieOptions = Omit<Partial<CookieOptions>, 'maxAge' | 'expires'>;
|
|
6
7
|
/**
|
|
7
8
|
* The bridge between the User provider and the
|
|
8
9
|
* Guard
|
|
@@ -43,7 +44,6 @@ export type BaseJwtContent = {
|
|
|
43
44
|
export type JwtGuardOptions<RealUser extends any = unknown> = {
|
|
44
45
|
secret: string;
|
|
45
46
|
jwks?: Options;
|
|
46
|
-
jwksManager?: JwksManager;
|
|
47
47
|
refreshTokenUserProvider?: AccessTokensUserProviderContract<RealUser>;
|
|
48
48
|
tokenName?: string;
|
|
49
49
|
expiresIn?: number | StringValue;
|
|
@@ -51,5 +51,6 @@ export type JwtGuardOptions<RealUser extends any = unknown> = {
|
|
|
51
51
|
useCookies?: boolean;
|
|
52
52
|
useCookiesForRefreshToken?: boolean;
|
|
53
53
|
refreshTokenAbilities?: string[];
|
|
54
|
+
cookie?: JwtCookieOptions;
|
|
54
55
|
content?: (user: JwtGuardUser<RealUser>) => Record<string, any>;
|
|
55
56
|
};
|
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.
|
|
4
|
+
"version": "0.7.1",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.6.0"
|
|
7
7
|
},
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@adonisjs/assembler": "^7.8.2",
|
|
38
|
-
"@adonisjs/auth": "^9.
|
|
39
|
-
"@adonisjs/core": "^6.
|
|
38
|
+
"@adonisjs/auth": "^9.6.0",
|
|
39
|
+
"@adonisjs/core": "^6.20.0",
|
|
40
40
|
"@adonisjs/eslint-config": "^2.1.2",
|
|
41
41
|
"@adonisjs/lucid": "^21.8.2",
|
|
42
42
|
"@adonisjs/prettier-config": "^1.4.5",
|
|
@@ -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": "^11.1.0",
|
|
48
|
+
"@swc/core": "^1.15.11",
|
|
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.11",
|
|
56
|
+
"np": "^11.0.2",
|
|
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",
|
|
@@ -98,4 +100,4 @@
|
|
|
98
100
|
"tests/**"
|
|
99
101
|
]
|
|
100
102
|
}
|
|
101
|
-
}
|
|
103
|
+
}
|