nestjs-keycloak-auth 1.0.5 → 1.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 +12 -6
- package/dist/interface/keycloak-auth-options.interface.d.ts +10 -0
- package/dist/services/backchannel-logout.service.d.ts +10 -0
- package/dist/services/backchannel-logout.service.js +28 -3
- package/dist/services/token-validation.service.d.ts +1 -0
- package/dist/services/token-validation.service.js +7 -1
- package/dist/token/keycloak-token.d.ts +1 -1
- package/dist/token/keycloak-token.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,8 @@ KeycloakAuthModule.register({
|
|
|
63
63
|
bearerOnly: true,
|
|
64
64
|
policyEnforcement: PolicyEnforcementMode.PERMISSIVE, // optional
|
|
65
65
|
tokenValidation: TokenValidation.ONLINE, // optional
|
|
66
|
+
backchannelLogoutTtlMs: 24 * 60 * 60 * 1000, // optional, defaults to 24 hours
|
|
67
|
+
clockSkewSec: 5, // optional, defaults to 0; used by OFFLINE expiration checks
|
|
66
68
|
});
|
|
67
69
|
```
|
|
68
70
|
|
|
@@ -98,6 +100,8 @@ export class KeycloakConfigService implements KeycloakAuthOptionsFactory {
|
|
|
98
100
|
bearerOnly: true,
|
|
99
101
|
policyEnforcement: PolicyEnforcementMode.PERMISSIVE,
|
|
100
102
|
tokenValidation: TokenValidation.ONLINE,
|
|
103
|
+
backchannelLogoutTtlMs: 24 * 60 * 60 * 1000,
|
|
104
|
+
clockSkewSec: 5,
|
|
101
105
|
};
|
|
102
106
|
}
|
|
103
107
|
}
|
|
@@ -346,12 +350,14 @@ Current test setup uses Jest + ts-jest and is configured to enforce 100% global
|
|
|
346
350
|
|
|
347
351
|
### Nest Keycloak Options
|
|
348
352
|
|
|
349
|
-
| Option
|
|
350
|
-
|
|
|
351
|
-
| policyEnforcement
|
|
352
|
-
| tokenValidation
|
|
353
|
-
| multiTenant
|
|
354
|
-
| roleMerge
|
|
353
|
+
| Option | Description | Required | Default |
|
|
354
|
+
| ---------------------- | ------------------------------------------------------------------------ | -------- | ---------- |
|
|
355
|
+
| policyEnforcement | Sets the policy enforcement mode | no | PERMISSIVE |
|
|
356
|
+
| tokenValidation | Sets the token validation method | no | ONLINE |
|
|
357
|
+
| multiTenant | Sets options for multi-tenant configuration | no | - |
|
|
358
|
+
| roleMerge | Sets the merge mode for `@Roles` decorator | no | OVERRIDE |
|
|
359
|
+
| backchannelLogoutTtlMs | TTL for in-memory back-channel logout revocation entries in milliseconds | no | 86400000 |
|
|
360
|
+
| clockSkewSec | Allowed clock skew for OFFLINE token expiration checks in seconds | no | 0 |
|
|
355
361
|
|
|
356
362
|
### Common Keycloak Config Fields
|
|
357
363
|
|
|
@@ -45,6 +45,16 @@ export interface NestKeycloakConfig {
|
|
|
45
45
|
* Role merging options.
|
|
46
46
|
*/
|
|
47
47
|
roleMerge?: RoleMerge;
|
|
48
|
+
/**
|
|
49
|
+
* TTL for in-memory back-channel logout revocation entries in milliseconds.
|
|
50
|
+
* Defaults to 24 hours.
|
|
51
|
+
*/
|
|
52
|
+
backchannelLogoutTtlMs?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Allowed clock skew for OFFLINE token expiration checks in seconds.
|
|
55
|
+
* Defaults to 0.
|
|
56
|
+
*/
|
|
57
|
+
clockSkewSec?: number;
|
|
48
58
|
}
|
|
49
59
|
/**
|
|
50
60
|
* Keycloak Connect options.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KeycloakAuthConfig } from '../interface/keycloak-auth-options.interface';
|
|
1
2
|
/**
|
|
2
3
|
* In-memory store tracking revoked sessions and users from
|
|
3
4
|
* OIDC back-channel logout tokens.
|
|
@@ -6,10 +7,13 @@
|
|
|
6
7
|
* (default 24 hours) to prevent unbounded memory growth.
|
|
7
8
|
*/
|
|
8
9
|
export declare class BackchannelLogoutService {
|
|
10
|
+
private readonly keycloakOpts;
|
|
9
11
|
private readonly logger;
|
|
12
|
+
private static readonly DEFAULT_TTL_MS;
|
|
10
13
|
private readonly revokedSessions;
|
|
11
14
|
private readonly revokedUsers;
|
|
12
15
|
private readonly ttlMs;
|
|
16
|
+
constructor(keycloakOpts: KeycloakAuthConfig);
|
|
13
17
|
/**
|
|
14
18
|
* Mark a session and/or user as revoked.
|
|
15
19
|
* At least one of `sid` or `sub` must be provided.
|
|
@@ -27,4 +31,10 @@ export declare class BackchannelLogoutService {
|
|
|
27
31
|
* Remove entries older than the TTL to prevent unbounded growth.
|
|
28
32
|
*/
|
|
29
33
|
private cleanup;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the TTL for in-memory revocation entries.
|
|
36
|
+
* Uses `backchannelLogoutTtlMs` when configured with a valid positive number,
|
|
37
|
+
* otherwise falls back to the default 24-hour retention window.
|
|
38
|
+
*/
|
|
39
|
+
private resolveTtlMs;
|
|
30
40
|
}
|
|
@@ -5,9 +5,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
7
|
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
8
14
|
var BackchannelLogoutService_1;
|
|
9
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
16
|
exports.BackchannelLogoutService = void 0;
|
|
17
|
+
const constants_1 = require("../constants");
|
|
11
18
|
const common_1 = require("@nestjs/common");
|
|
12
19
|
/**
|
|
13
20
|
* In-memory store tracking revoked sessions and users from
|
|
@@ -17,11 +24,12 @@ const common_1 = require("@nestjs/common");
|
|
|
17
24
|
* (default 24 hours) to prevent unbounded memory growth.
|
|
18
25
|
*/
|
|
19
26
|
let BackchannelLogoutService = BackchannelLogoutService_1 = class BackchannelLogoutService {
|
|
20
|
-
constructor() {
|
|
27
|
+
constructor(keycloakOpts) {
|
|
28
|
+
this.keycloakOpts = keycloakOpts;
|
|
21
29
|
this.logger = new common_1.Logger(BackchannelLogoutService_1.name);
|
|
22
30
|
this.revokedSessions = new Map(); // sid → revokedAt
|
|
23
31
|
this.revokedUsers = new Map(); // sub → revokedAt
|
|
24
|
-
this.ttlMs =
|
|
32
|
+
this.ttlMs = this.resolveTtlMs();
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* Mark a session and/or user as revoked.
|
|
@@ -93,8 +101,25 @@ let BackchannelLogoutService = BackchannelLogoutService_1 = class BackchannelLog
|
|
|
93
101
|
}
|
|
94
102
|
}
|
|
95
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the TTL for in-memory revocation entries.
|
|
106
|
+
* Uses `backchannelLogoutTtlMs` when configured with a valid positive number,
|
|
107
|
+
* otherwise falls back to the default 24-hour retention window.
|
|
108
|
+
*/
|
|
109
|
+
resolveTtlMs() {
|
|
110
|
+
const configuredTtl = this.keycloakOpts.backchannelLogoutTtlMs;
|
|
111
|
+
if (typeof configuredTtl === 'number' &&
|
|
112
|
+
Number.isFinite(configuredTtl) &&
|
|
113
|
+
configuredTtl > 0) {
|
|
114
|
+
return configuredTtl;
|
|
115
|
+
}
|
|
116
|
+
return BackchannelLogoutService_1.DEFAULT_TTL_MS;
|
|
117
|
+
}
|
|
96
118
|
};
|
|
97
119
|
exports.BackchannelLogoutService = BackchannelLogoutService;
|
|
120
|
+
BackchannelLogoutService.DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
98
121
|
exports.BackchannelLogoutService = BackchannelLogoutService = BackchannelLogoutService_1 = __decorate([
|
|
99
|
-
(0, common_1.Injectable)()
|
|
122
|
+
(0, common_1.Injectable)(),
|
|
123
|
+
__param(0, (0, common_1.Inject)(constants_1.KEYCLOAK_AUTH_OPTIONS)),
|
|
124
|
+
__metadata("design:paramtypes", [Object])
|
|
100
125
|
], BackchannelLogoutService);
|
|
@@ -123,7 +123,7 @@ let TokenValidationService = TokenValidationService_1 = class TokenValidationSer
|
|
|
123
123
|
return false;
|
|
124
124
|
}
|
|
125
125
|
// Check expiry
|
|
126
|
-
if (token.isExpired()) {
|
|
126
|
+
if (token.isExpired(this.getClockSkewSec())) {
|
|
127
127
|
this.logger.verbose('invalid token (expired)');
|
|
128
128
|
return false;
|
|
129
129
|
}
|
|
@@ -200,6 +200,12 @@ let TokenValidationService = TokenValidationService_1 = class TokenValidationSer
|
|
|
200
200
|
// crypto.verify() auto-detects key type (RSA vs EC) from the KeyObject
|
|
201
201
|
return crypto.verify(hash, Buffer.from(signed), keyObj, signature);
|
|
202
202
|
}
|
|
203
|
+
getClockSkewSec() {
|
|
204
|
+
const clockSkewSec = this.keycloakOpts.clockSkewSec;
|
|
205
|
+
return typeof clockSkewSec === 'number' && Number.isFinite(clockSkewSec) && clockSkewSec > 0
|
|
206
|
+
? clockSkewSec
|
|
207
|
+
: 0;
|
|
208
|
+
}
|
|
203
209
|
};
|
|
204
210
|
exports.TokenValidationService = TokenValidationService;
|
|
205
211
|
TokenValidationService.ALLOWED_ALGS = new Set([
|
|
@@ -49,7 +49,7 @@ export declare class KeycloakToken {
|
|
|
49
49
|
* Check if the token is expired.
|
|
50
50
|
* Matches keycloak-connect's behavior: exp=0 is considered expired.
|
|
51
51
|
*/
|
|
52
|
-
isExpired(): boolean;
|
|
52
|
+
isExpired(clockSkewSec?: number): boolean;
|
|
53
53
|
/**
|
|
54
54
|
* Get the raw JWT string.
|
|
55
55
|
*/
|
|
@@ -92,8 +92,8 @@ class KeycloakToken {
|
|
|
92
92
|
* Check if the token is expired.
|
|
93
93
|
* Matches keycloak-connect's behavior: exp=0 is considered expired.
|
|
94
94
|
*/
|
|
95
|
-
isExpired() {
|
|
96
|
-
return this.content.exp * 1000 < Date.now();
|
|
95
|
+
isExpired(clockSkewSec = 0) {
|
|
96
|
+
return (this.content.exp + clockSkewSec) * 1000 < Date.now();
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* Get the raw JWT string.
|