@tidecloak/verify 0.13.30 → 0.13.31
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 +23 -7
- package/dist/cjs/TideJWT.js +39 -14
- package/dist/cjs/package.json +1 -0
- package/dist/esm/TideJWT.js +32 -10
- package/dist/esm/package.json +1 -0
- package/dist/types/TideJWT.d.ts +3 -1
- package/dist/types/TideJWT.d.ts.map +1 -1
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight utility for server‑side verification of TideCloak‑issued JSON Web Tokens (JWTs).
|
|
4
4
|
|
|
5
|
-
This package exports a single function, `verifyTideCloakToken`, which you can use in your Next.js API routes, Node.js servers, or any backend to verify the
|
|
5
|
+
This package exports a single function, `verifyTideCloakToken`, which you can use in your Next.js API routes, Node.js servers, or any backend to verify the signature, issuer, authorized party (`azp`), and roles of a JWT issued by your TideCloak realm.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -47,11 +47,23 @@ Internally, `verifyTideCloakToken` uses the [jose](https://github.com/panva/jose
|
|
|
47
47
|
1. Ensure a token is present.
|
|
48
48
|
2. Construct the correct issuer URL from `config['auth-server-url']` and `config.realm`.
|
|
49
49
|
3. Choose between a local JWK Set (`config.jwk.keys`) or fetch the JWK Set remotely from Tidecloak.
|
|
50
|
-
4. Verify the token's signature, issuer
|
|
51
|
-
5.
|
|
52
|
-
6.
|
|
50
|
+
4. Verify the token's signature against a **pinned algorithm allowlist** (`ES256`, `ES384`, `ES512`, `EdDSA` by default), the `issuer`, and the standard time claims (`exp`/`nbf`) with a small `clockTolerance`.
|
|
51
|
+
5. Verify the `azp` (authorized party) against `config.resource` — **only when `resource` is configured**.
|
|
52
|
+
6. Extract realm (`payload.realm_access.roles`) and client (`payload.resource_access[resource].roles`) roles.
|
|
53
|
+
7. Check for at least one matching role if `allowedRoles` is specified.
|
|
53
54
|
|
|
54
|
-
On any failure, it logs
|
|
55
|
+
On any failure, it logs the error message to the console and returns `null`.
|
|
56
|
+
|
|
57
|
+
> **Note:** the algorithm allowlist closes algorithm-confusion attacks — without it, `jose` would accept any algorithm a key in the set can validate. A `null` result collapses both invalid tokens and infrastructure failures (e.g. an unreachable remote JWKS endpoint), so treat `null` as "not authorized" and monitor your JWKS reachability separately.
|
|
58
|
+
|
|
59
|
+
### Optional config fields
|
|
60
|
+
|
|
61
|
+
You can tune verification by adding these optional fields to the `config` object:
|
|
62
|
+
|
|
63
|
+
| Field | Type | Default | Description |
|
|
64
|
+
| --------------------------- | ---------------------- | ---------------------------------------- | --------------------------------------------------------------------------- |
|
|
65
|
+
| `tokenSignatureAlgorithms` | `string[]` | `['ES256','ES384','ES512','EdDSA']` | Allowed JWS signature algorithms. Override if your realm signs with others (e.g. `['RS256']`). |
|
|
66
|
+
| `clockTolerance` | `number` \| `string` | `'5s'` | Allowed clock skew between issuer and verifier (seconds, or a jose duration string). |
|
|
55
67
|
|
|
56
68
|
---
|
|
57
69
|
|
|
@@ -172,10 +184,14 @@ export async function GET(req: NextRequest) {
|
|
|
172
184
|
interface TidecloakConfig {
|
|
173
185
|
realm: string;
|
|
174
186
|
'auth-server-url': string;
|
|
175
|
-
resource
|
|
187
|
+
resource?: string;
|
|
176
188
|
publicClient?: boolean;
|
|
177
189
|
confidentialPort?: number;
|
|
178
|
-
jwk?: { keys: Array<{ kid: string; kty: string; alg
|
|
190
|
+
jwk?: { keys: Array<{ kid: string; kty: string; alg?: string; use?: string; x?: string; crv?: string; n?: string; e?: string }> };
|
|
191
|
+
/** Allowed JWS signature algorithms. Default: ['ES256','ES384','ES512','EdDSA']. */
|
|
192
|
+
tokenSignatureAlgorithms?: string[];
|
|
193
|
+
/** Allowed clock skew (seconds or a jose duration string). Default: '5s'. */
|
|
194
|
+
clockTolerance?: number | string;
|
|
179
195
|
[key: string]: unknown;
|
|
180
196
|
}
|
|
181
197
|
|
package/dist/cjs/TideJWT.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyTideCloakToken = verifyTideCloakToken;
|
|
4
|
+
const jose_1 = require("jose");
|
|
5
|
+
/**
|
|
6
|
+
* Signature algorithms TideCloak realms issue tokens with. Pinning these prevents
|
|
7
|
+
* algorithm-confusion attacks: without an explicit allowlist `jose` accepts any
|
|
8
|
+
* algorithm a key in the set can validate (and falls back to permissive behaviour
|
|
9
|
+
* for keys that omit an `alg`). Override via `config.tokenSignatureAlgorithms`.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_ALLOWED_ALGORITHMS = ["ES256", "ES384", "ES512", "EdDSA"];
|
|
2
12
|
/**
|
|
3
13
|
* Verify a TideCloak-issued JWT on the server side using your imported config object.
|
|
4
14
|
*
|
|
5
|
-
* @param {object} config - Imported TideCloak configuration (parsed JSON).
|
|
15
|
+
* @param {object} config - Imported TideCloak configuration (parsed JSON). May also
|
|
16
|
+
* carry optional `tokenSignatureAlgorithms` (string[]) and `clockTolerance`
|
|
17
|
+
* (number of seconds, or a jose duration string) to tune verification.
|
|
6
18
|
* @param {string} token - access token to verify.
|
|
7
19
|
* @param {string[]} [allowedRoles] - Array of Tidecloak realm or client roles; user must have at least one.
|
|
8
20
|
* @returns {Promise<object|null>} - The token payload if valid and role-check passes, otherwise null.
|
|
9
21
|
*/
|
|
10
|
-
|
|
11
|
-
var _a, _b, _c;
|
|
22
|
+
async function verifyTideCloakToken(config, token, allowedRoles = []) {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
12
24
|
try {
|
|
13
25
|
// Ensure token is provided
|
|
14
26
|
if (!token) {
|
|
@@ -24,18 +36,27 @@ export async function verifyTideCloakToken(config, token, allowedRoles = []) {
|
|
|
24
36
|
const issuer = `${baseUrl}${sep}realms/${config.realm}`;
|
|
25
37
|
// Determine JWK set (use local JWKs if provided, otherwise fetch remotely)
|
|
26
38
|
const jwkSet = config.jwk
|
|
27
|
-
? createLocalJWKSet(config.jwk)
|
|
28
|
-
: createRemoteJWKSet(new URL(`${issuer}/protocol/openid-connect/certs`));
|
|
29
|
-
// Verify
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
? (0, jose_1.createLocalJWKSet)(config.jwk)
|
|
40
|
+
: (0, jose_1.createRemoteJWKSet)(new URL(`${issuer}/protocol/openid-connect/certs`));
|
|
41
|
+
// Verify signature (with a pinned algorithm allowlist), issuer and time claims.
|
|
42
|
+
// `clockTolerance` allows a small amount of clock drift between issuer and verifier.
|
|
43
|
+
const algorithms = Array.isArray(config.tokenSignatureAlgorithms) && config.tokenSignatureAlgorithms.length > 0
|
|
44
|
+
? config.tokenSignatureAlgorithms
|
|
45
|
+
: DEFAULT_ALLOWED_ALGORITHMS;
|
|
46
|
+
const { payload } = await (0, jose_1.jwtVerify)(token, jwkSet, {
|
|
47
|
+
issuer,
|
|
48
|
+
algorithms,
|
|
49
|
+
clockTolerance: (_a = config.clockTolerance) !== null && _a !== void 0 ? _a : "5s",
|
|
50
|
+
});
|
|
51
|
+
// Verify authorized party (client). Only enforced when a `resource` (client id)
|
|
52
|
+
// is configured; without this guard an undefined client would reject every token.
|
|
32
53
|
const client = config["resource"];
|
|
33
|
-
if (payload.azp !== client) {
|
|
34
|
-
throw new Error(`AZP mismatch: expected '${
|
|
54
|
+
if (client !== undefined && client !== null && payload.azp !== client) {
|
|
55
|
+
throw new Error(`AZP mismatch: expected '${client}', got '${payload.azp}'`);
|
|
35
56
|
}
|
|
36
57
|
// Gather all user roles from realm and client roles for the specified resource from the config.
|
|
37
|
-
const realmRoles = ((
|
|
38
|
-
const clientRoles = ((
|
|
58
|
+
const realmRoles = ((_b = payload.realm_access) === null || _b === void 0 ? void 0 : _b.roles) || [];
|
|
59
|
+
const clientRoles = ((_d = (_c = payload.resource_access) === null || _c === void 0 ? void 0 : _c[client]) === null || _d === void 0 ? void 0 : _d.roles) || [];
|
|
39
60
|
const allRoles = new Set([...realmRoles, ...clientRoles]);
|
|
40
61
|
// If allowedRoles specified, ensure at least one match
|
|
41
62
|
if (allowedRoles.length > 0) {
|
|
@@ -47,7 +68,11 @@ export async function verifyTideCloakToken(config, token, allowedRoles = []) {
|
|
|
47
68
|
return payload;
|
|
48
69
|
}
|
|
49
70
|
catch (err) {
|
|
50
|
-
|
|
71
|
+
// Log only the message (not the error object, which can echo token-derived
|
|
72
|
+
// data). Note: this collapses both invalid tokens and infrastructure failures
|
|
73
|
+
// (e.g. an unreachable JWKS endpoint) into a `null` result, so callers cannot
|
|
74
|
+
// distinguish "forbidden" from "auth backend down".
|
|
75
|
+
console.error("[TideJWT] Token verification failed:", err instanceof Error ? err.message : err);
|
|
51
76
|
return null;
|
|
52
77
|
}
|
|
53
78
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
package/dist/esm/TideJWT.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { jwtVerify, createLocalJWKSet, createRemoteJWKSet } from "jose";
|
|
2
|
+
/**
|
|
3
|
+
* Signature algorithms TideCloak realms issue tokens with. Pinning these prevents
|
|
4
|
+
* algorithm-confusion attacks: without an explicit allowlist `jose` accepts any
|
|
5
|
+
* algorithm a key in the set can validate (and falls back to permissive behaviour
|
|
6
|
+
* for keys that omit an `alg`). Override via `config.tokenSignatureAlgorithms`.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_ALLOWED_ALGORITHMS = ["ES256", "ES384", "ES512", "EdDSA"];
|
|
2
9
|
/**
|
|
3
10
|
* Verify a TideCloak-issued JWT on the server side using your imported config object.
|
|
4
11
|
*
|
|
5
|
-
* @param {object} config - Imported TideCloak configuration (parsed JSON).
|
|
12
|
+
* @param {object} config - Imported TideCloak configuration (parsed JSON). May also
|
|
13
|
+
* carry optional `tokenSignatureAlgorithms` (string[]) and `clockTolerance`
|
|
14
|
+
* (number of seconds, or a jose duration string) to tune verification.
|
|
6
15
|
* @param {string} token - access token to verify.
|
|
7
16
|
* @param {string[]} [allowedRoles] - Array of Tidecloak realm or client roles; user must have at least one.
|
|
8
17
|
* @returns {Promise<object|null>} - The token payload if valid and role-check passes, otherwise null.
|
|
9
18
|
*/
|
|
10
19
|
export async function verifyTideCloakToken(config, token, allowedRoles = []) {
|
|
11
|
-
var _a, _b, _c;
|
|
20
|
+
var _a, _b, _c, _d;
|
|
12
21
|
try {
|
|
13
22
|
// Ensure token is provided
|
|
14
23
|
if (!token) {
|
|
@@ -26,16 +35,25 @@ export async function verifyTideCloakToken(config, token, allowedRoles = []) {
|
|
|
26
35
|
const jwkSet = config.jwk
|
|
27
36
|
? createLocalJWKSet(config.jwk)
|
|
28
37
|
: createRemoteJWKSet(new URL(`${issuer}/protocol/openid-connect/certs`));
|
|
29
|
-
// Verify
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
// Verify signature (with a pinned algorithm allowlist), issuer and time claims.
|
|
39
|
+
// `clockTolerance` allows a small amount of clock drift between issuer and verifier.
|
|
40
|
+
const algorithms = Array.isArray(config.tokenSignatureAlgorithms) && config.tokenSignatureAlgorithms.length > 0
|
|
41
|
+
? config.tokenSignatureAlgorithms
|
|
42
|
+
: DEFAULT_ALLOWED_ALGORITHMS;
|
|
43
|
+
const { payload } = await jwtVerify(token, jwkSet, {
|
|
44
|
+
issuer,
|
|
45
|
+
algorithms,
|
|
46
|
+
clockTolerance: (_a = config.clockTolerance) !== null && _a !== void 0 ? _a : "5s",
|
|
47
|
+
});
|
|
48
|
+
// Verify authorized party (client). Only enforced when a `resource` (client id)
|
|
49
|
+
// is configured; without this guard an undefined client would reject every token.
|
|
32
50
|
const client = config["resource"];
|
|
33
|
-
if (payload.azp !== client) {
|
|
34
|
-
throw new Error(`AZP mismatch: expected '${
|
|
51
|
+
if (client !== undefined && client !== null && payload.azp !== client) {
|
|
52
|
+
throw new Error(`AZP mismatch: expected '${client}', got '${payload.azp}'`);
|
|
35
53
|
}
|
|
36
54
|
// Gather all user roles from realm and client roles for the specified resource from the config.
|
|
37
|
-
const realmRoles = ((
|
|
38
|
-
const clientRoles = ((
|
|
55
|
+
const realmRoles = ((_b = payload.realm_access) === null || _b === void 0 ? void 0 : _b.roles) || [];
|
|
56
|
+
const clientRoles = ((_d = (_c = payload.resource_access) === null || _c === void 0 ? void 0 : _c[client]) === null || _d === void 0 ? void 0 : _d.roles) || [];
|
|
39
57
|
const allRoles = new Set([...realmRoles, ...clientRoles]);
|
|
40
58
|
// If allowedRoles specified, ensure at least one match
|
|
41
59
|
if (allowedRoles.length > 0) {
|
|
@@ -47,7 +65,11 @@ export async function verifyTideCloakToken(config, token, allowedRoles = []) {
|
|
|
47
65
|
return payload;
|
|
48
66
|
}
|
|
49
67
|
catch (err) {
|
|
50
|
-
|
|
68
|
+
// Log only the message (not the error object, which can echo token-derived
|
|
69
|
+
// data). Note: this collapses both invalid tokens and infrastructure failures
|
|
70
|
+
// (e.g. an unreachable JWKS endpoint) into a `null` result, so callers cannot
|
|
71
|
+
// distinguish "forbidden" from "auth backend down".
|
|
72
|
+
console.error("[TideJWT] Token verification failed:", err instanceof Error ? err.message : err);
|
|
51
73
|
return null;
|
|
52
74
|
}
|
|
53
75
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
package/dist/types/TideJWT.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Verify a TideCloak-issued JWT on the server side using your imported config object.
|
|
3
3
|
*
|
|
4
|
-
* @param {object} config - Imported TideCloak configuration (parsed JSON).
|
|
4
|
+
* @param {object} config - Imported TideCloak configuration (parsed JSON). May also
|
|
5
|
+
* carry optional `tokenSignatureAlgorithms` (string[]) and `clockTolerance`
|
|
6
|
+
* (number of seconds, or a jose duration string) to tune verification.
|
|
5
7
|
* @param {string} token - access token to verify.
|
|
6
8
|
* @param {string[]} [allowedRoles] - Array of Tidecloak realm or client roles; user must have at least one.
|
|
7
9
|
* @returns {Promise<object|null>} - The token payload if valid and role-check passes, otherwise null.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TideJWT.d.ts","sourceRoot":"","sources":["../../src/TideJWT.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TideJWT.d.ts","sourceRoot":"","sources":["../../src/TideJWT.js"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,6CAPW,MAAM,SAGN,MAAM,iBACN,MAAM,EAAE,GACN,OAAO,CAAC,MAAM,GAAC,IAAI,CAAC,CAmEhC"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tidecloak/verify",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.31",
|
|
4
4
|
"description": "A lightweight utility for server-side verification of TideCloak-issued JSON Web Tokens (JWTs).",
|
|
5
|
+
"main": "./dist/cjs/TideJWT.js",
|
|
6
|
+
"module": "./dist/esm/TideJWT.js",
|
|
7
|
+
"types": "./dist/types/TideJWT.d.ts",
|
|
5
8
|
"exports": {
|
|
6
9
|
".": {
|
|
7
|
-
"import": "./dist/esm/TideJWT.js",
|
|
8
10
|
"types": "./dist/types/TideJWT.d.ts",
|
|
9
|
-
"
|
|
11
|
+
"import": "./dist/esm/TideJWT.js",
|
|
12
|
+
"require": "./dist/cjs/TideJWT.js",
|
|
13
|
+
"default": "./dist/esm/TideJWT.js"
|
|
10
14
|
}
|
|
11
15
|
},
|
|
12
16
|
"files": [
|
|
@@ -15,7 +19,8 @@
|
|
|
15
19
|
"scripts": {
|
|
16
20
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
17
21
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
18
|
-
"build": "npm run build:cjs && npm run build:esm",
|
|
22
|
+
"build": "npm run build:cjs && npm run build:esm && node scripts/postbuild.cjs",
|
|
23
|
+
"test": "node --test test/*.test.mjs",
|
|
19
24
|
"prepare": "npm run build"
|
|
20
25
|
},
|
|
21
26
|
"publishConfig": {
|