@t4h.framework/jwks 0.7.6 → 0.7.8
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/.ai/skills/framework-jwks/SKILL.md +79 -247
- package/CHANGELOG.md +20 -0
- package/README.md +15 -57
- package/dist/helpers/validateJwt.js +1 -1
- package/dist/helpers/validateJwt.js.map +1 -1
- package/dist/jwks-callback.d.ts.map +1 -1
- package/dist/jwks-callback.js +9 -5
- package/dist/jwks-callback.js.map +1 -1
- package/dist/models/JWKSAuthorization.d.ts +1 -1
- package/dist/models/JWKSAuthorization.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -1,276 +1,108 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framework-jwks
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Verifies RS256 JWTs and detached public-key signatures against a JWKS
|
|
5
|
+
endpoint with @t4h.framework/jwks. Use when a T4H Framework workflow or App
|
|
6
|
+
needs JWKSAuthorization, when an async activity such as webhook() declares
|
|
7
|
+
auth { type: 'jwt' } or auth { type: 'jwks' } for its callbacks, or when the
|
|
8
|
+
user mentions a JWKS URL, .well-known/jwks.json, kid, issuer, audience,
|
|
9
|
+
JWKSAlgorithm, a bearer JWT (Google Pub/Sub push, Cloud Tasks) or a signature
|
|
10
|
+
verified with a key published in a JWKS.
|
|
10
11
|
---
|
|
11
12
|
|
|
12
13
|
# @t4h.framework/jwks
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
The package has two uses. Pick the one that matches the task:
|
|
15
16
|
|
|
16
|
-
**
|
|
17
|
+
- **Protect a workflow or an `App`**: `new JWKSAuthorization(context)`.
|
|
18
|
+
- **Verify the callbacks of an async activity**: declare
|
|
19
|
+
`auth: { type: 'jwt', … }` or `auth: { type: 'jwks', … }` on the activity
|
|
20
|
+
(for `webhook()`, see the **framework-webhook** skill). The host verifies
|
|
21
|
+
each callback with `@t4h.framework/jwks/callback`. You do not import it.
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
## Protect a workflow or an App
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Imports
|
|
25
|
-
|
|
26
|
-
| Entry | Purpose |
|
|
27
|
-
|-------|---------|
|
|
28
|
-
| `@t4h.framework/jwks` | `JWKSAuthorization`, `JWKSAuthorizationContext`, `JWKSAuthorizationManifest`, `JWKSAlgorithm`, re-exports authorize module |
|
|
29
|
-
| `@t4h.framework/jwks/authorize` | Default async handler: `(context) => Promise<boolean>` |
|
|
30
|
-
| `@t4h.framework/jwks/callback` | Default export `jwksCallback`: `(auth, delivery) => Promise<boolean>`, the authorizer hosts pass to the core `AsyncActivityCallback.authorize` for `jwt` (bearer JWT) and `jwks` (detached signature over the body, key resolved by `kid` from a JWKS URL) |
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
import {
|
|
34
|
-
JWKSAuthorization,
|
|
35
|
-
type JWKSAuthorizationContext,
|
|
36
|
-
JWKSAlgorithm,
|
|
37
|
-
} from '@t4h.framework/jwks'
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Do **not** assume OIDC discovery, env-variable mapping, or `credentialSource` helpers—this package does not read HTTP headers or env. Your app or hosting layer supplies `signature`, `rawPayload`, and `url`.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Wiring authorization (core)
|
|
45
|
-
|
|
46
|
-
`JWKSAuthorization` extends core `Authorization`. Attach to a **Workflow** or **App**; `toManifest()` registers the handler module for the platform.
|
|
25
|
+
The handler picks the path from `signature`: a value that decodes as a JWT
|
|
26
|
+
takes the JWT path, any other value takes the raw-payload path.
|
|
47
27
|
|
|
48
28
|
```typescript
|
|
49
|
-
import { App, Workflow } from '@t4h.framework/core'
|
|
50
29
|
import { JWKSAuthorization } from '@t4h.framework/jwks'
|
|
51
30
|
|
|
52
|
-
const authorization = new JWKSAuthorization({
|
|
31
|
+
export const authorization = new JWKSAuthorization({
|
|
53
32
|
url: 'https://auth.example.com/.well-known/jwks.json',
|
|
54
|
-
signature: '<
|
|
55
|
-
rawPayload: '
|
|
33
|
+
signature: '<jwt-from-the-request>',
|
|
34
|
+
rawPayload: '',
|
|
35
|
+
issuer: 'https://auth.example.com/',
|
|
56
36
|
audience: 'https://api.example.com',
|
|
57
37
|
})
|
|
58
|
-
|
|
59
|
-
const workflow = new Workflow(
|
|
60
|
-
{ id: 'protected/v1', authorization },
|
|
61
|
-
async () => ({ ok: true }),
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
const app = new App({
|
|
65
|
-
id: 'my-app',
|
|
66
|
-
workflows: [workflow],
|
|
67
|
-
authorization, // optional app-level default
|
|
68
|
-
})
|
|
69
38
|
```
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
pathToModule: '@t4h.framework/jwks/authorize',
|
|
76
|
-
context: {
|
|
77
|
-
url: string
|
|
78
|
-
signature: string
|
|
79
|
-
rawPayload: string
|
|
80
|
-
issuer?: string
|
|
81
|
-
audience?: string
|
|
82
|
-
kid?: string
|
|
83
|
-
algorithm?: string
|
|
84
|
-
signatureFormat?: string
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Optional manifest fields are still present as `undefined` when omitted (`toManifest` copies all keys).
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## Context: `JWKSAuthorizationContext`
|
|
40
|
+
Attach it to the `authorization` option of a `Workflow` or an `App`. For a
|
|
41
|
+
detached signature, give the signed bytes in `rawPayload` and set `kid`,
|
|
42
|
+
`algorithm` and `signatureFormat`.
|
|
94
43
|
|
|
95
|
-
|
|
96
|
-
|-------|----------|-------------|
|
|
97
|
-
| `url` | Yes | JWKS document URL (`jwks-rsa` `jwksUri`). |
|
|
98
|
-
| `signature` | Yes | Credential to verify: JWT string, or detached signature for non-JWT path. Empty string → handler returns `false`. |
|
|
99
|
-
| `rawPayload` | Yes | Payload bytes as string for non-JWT verification; for JWT path, tests pass the same string as `signature` (handler only uses `signature` on JWT path). |
|
|
100
|
-
| `issuer` | No | When set, passed to `jwt.verify` as expected `iss`. Mismatch **rejects** (throws). |
|
|
101
|
-
| `audience` | No | When set, passed to `jwt.verify` as expected `aud`. |
|
|
102
|
-
| `kid` | Conditional | Required when `signature` is **not** a decodable JWT (non-JWT path). |
|
|
103
|
-
| `algorithm` | Conditional | Non-JWT path: **enum key** on `JWKSAlgorithm` (e.g. `RS256`), not the Node crypto string. |
|
|
104
|
-
| `signatureFormat` | Conditional | Non-JWT path: encoding for the signature buffer (`hex`, `base64`, etc.—`BinaryToTextEncoding`). |
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Runtime handler (`@t4h.framework/jwks/authorize`)
|
|
109
|
-
|
|
110
|
-
Default export:
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
(options: JWKSAuthorizationContext) => Promise<boolean>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Behavior (from `jwks-authorize.ts` and tests):
|
|
117
|
-
|
|
118
|
-
1. If `signature` is falsy → **`false`** (no throw).
|
|
119
|
-
2. Build `JwksClient({ jwksUri: options.url })`.
|
|
120
|
-
3. `jwt.decode(signature, { complete: true })`:
|
|
121
|
-
- **Decoded (JWT path)** → `validateJwt(signature, client, { issuer, audience })`.
|
|
122
|
-
- **Not decoded (non-JWT path)** → require `kid`, `algorithm`, `signatureFormat`; else **throw** with fixed messages (see below).
|
|
123
|
-
|
|
124
|
-
### JWT path (`validateJwt`)
|
|
125
|
-
|
|
126
|
-
- Algorithm must be **RS256** (`decoded.header.alg === 'RS256'`); otherwise **`false`** (e.g. HS256 tokens return `false`, not throw).
|
|
127
|
-
- Header must include **`kid`**; used to `getSigningKey(kid)` from JWKS.
|
|
128
|
-
- **`exp` enforced** (`ignoreExpiration: false`). Expired or wrong signing key → **`jwt.verify` rejects** (promise rejection / throw from handler).
|
|
129
|
-
- Optional `issuer` / `audience` from context applied when present.
|
|
130
|
-
|
|
131
|
-
### Non-JWT path
|
|
132
|
-
|
|
133
|
-
Uses Node `crypto.createVerify` with public key from JWKS:
|
|
44
|
+
## Verify async activity callbacks
|
|
134
45
|
|
|
135
46
|
```typescript
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Throws if any required field is missing:
|
|
141
|
-
|
|
142
|
-
| Missing | Error message |
|
|
143
|
-
|---------|----------------|
|
|
144
|
-
| `kid` | `KID is required for payload verification` |
|
|
145
|
-
| `algorithm` | `Algorithm is required for payload verification` |
|
|
146
|
-
| `signatureFormat` | `Signature format is required for payload verification` |
|
|
147
|
-
|
|
148
|
-
`algorithm` must be a **key** of `JWKSAlgorithm`:
|
|
149
|
-
|
|
150
|
-
```typescript
|
|
151
|
-
export enum JWKSAlgorithm {
|
|
152
|
-
RS256 = 'RSA-SHA256',
|
|
153
|
-
RS384 = 'RSA-SHA384',
|
|
154
|
-
RS512 = 'RSA-SHA512',
|
|
155
|
-
PS256 = 'RSA-PSS-SHA256',
|
|
156
|
-
PS384 = 'RSA-PSS-SHA384',
|
|
157
|
-
PS512 = 'RSA-PSS-SHA512',
|
|
158
|
-
ES256 = 'ECDSA-SHA256',
|
|
159
|
-
ES384 = 'ECDSA-SHA384',
|
|
160
|
-
ES512 = 'ECDSA-SHA512',
|
|
47
|
+
auth: {
|
|
48
|
+
type: ('jwt', jwksUri, issuer, audience)
|
|
161
49
|
}
|
|
162
50
|
```
|
|
163
51
|
|
|
164
|
-
Handler uses `JWKSAlgorithm[algorithm]` as the `createVerify` algorithm name. Pass `algorithm: 'RS256'`, not `'RSA-SHA256'`.
|
|
165
|
-
|
|
166
|
-
Returns **`true`** on successful verification, **`false`** when verification fails (non-JWT invalid signature).
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## Examples
|
|
171
|
-
|
|
172
|
-
### RS256 JWT (typical API Bearer token)
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
import { JWKSAuthorization } from '@t4h.framework/jwks'
|
|
176
|
-
|
|
177
|
-
const authorization = new JWKSAuthorization({
|
|
178
|
-
url: 'https://id.example.com/.well-known/jwks.json',
|
|
179
|
-
signature: bearerToken, // without "Bearer " prefix if your layer strips it
|
|
180
|
-
rawPayload: bearerToken,
|
|
181
|
-
issuer: 'https://issuer.example.com',
|
|
182
|
-
audience: 'my-api',
|
|
183
|
-
})
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Token must be signed RS256 with `kid` in header. Platform invokes `@t4h.framework/jwks/authorize` with manifest `context` when the protected workflow runs.
|
|
187
|
-
|
|
188
|
-
### Detached signature (non-JWT)
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
new JWKSAuthorization({
|
|
192
|
-
url: 'https://id.example.com/jwks',
|
|
193
|
-
signature: detachedSignatureHex,
|
|
194
|
-
rawPayload: canonicalPayloadString,
|
|
195
|
-
kid: 'key-1',
|
|
196
|
-
algorithm: 'RS256',
|
|
197
|
-
signatureFormat: 'hex',
|
|
198
|
-
})
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
If `signature` accidentally looks like a JWT to `jwt.decode`, the JWT path runs instead—structure payloads accordingly.
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## Return values vs errors
|
|
206
|
-
|
|
207
|
-
| Situation | Result |
|
|
208
|
-
|-----------|--------|
|
|
209
|
-
| Empty `signature` | `false` |
|
|
210
|
-
| Valid RS256 JWT, matching JWKS key | `true` |
|
|
211
|
-
| JWT wrong alg (e.g. HS256), no `kid`, undecodable token | `false` |
|
|
212
|
-
| Expired JWT, issuer mismatch, signature mismatch | **Rejected** (throw from `jwt.verify`) |
|
|
213
|
-
| Non-JWT missing `kid` / `algorithm` / `signatureFormat` | **Throw** (configuration error) |
|
|
214
|
-
| Non-JWT verify fails | `false` |
|
|
215
|
-
|
|
216
|
-
Design callers so manifest context is complete **before** the handler runs; missing non-JWT fields are programmer errors, not silent denials.
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## Testing the handler locally
|
|
221
|
-
|
|
222
|
-
Unit tests mock `jwks-rsa` and use real RSA key pairs + `jsonwebtoken`:
|
|
223
|
-
|
|
224
52
|
```typescript
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
53
|
+
auth: {
|
|
54
|
+
type: 'jwks',
|
|
55
|
+
jwksUri: 'https://partner.example.com/.well-known/jwks.json',
|
|
56
|
+
header: 'x-signature',
|
|
57
|
+
kidHeader: 'x-key-id',
|
|
230
58
|
algorithm: 'RS256',
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
jwks({
|
|
237
|
-
url: 'https://example.com/jwks',
|
|
238
|
-
signature: token,
|
|
239
|
-
rawPayload: token,
|
|
240
|
-
}),
|
|
241
|
-
).resolves.toBe(true)
|
|
59
|
+
signatureFormat: 'base64',
|
|
60
|
+
timestampHeader: 'x-timestamp',
|
|
61
|
+
toleranceInSeconds: 300,
|
|
62
|
+
signedPayload: '{timestamp}.{body}',
|
|
63
|
+
}
|
|
242
64
|
```
|
|
243
65
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
##
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
66
|
+
On `jwks`, each of `kid`, `algorithm` and `signatureFormat` is a fixed value or
|
|
67
|
+
a `*Header` name. A fixed value wins. `signatureFormat` defaults to `'hex'`.
|
|
68
|
+
|
|
69
|
+
## Traps
|
|
70
|
+
|
|
71
|
+
- The field is `signature`. There is no `value` field.
|
|
72
|
+
- The JWT path accepts only RS256 tokens with a `kid` in the header. HS256 or
|
|
73
|
+
ES256 tokens give `false`.
|
|
74
|
+
- On the JWT path `rawPayload` is not used. Pass `''`.
|
|
75
|
+
- `algorithm` is a `JWKSAlgorithm` KEY such as `'RS256'`, not the Node name
|
|
76
|
+
`'RSA-SHA256'`. Today only `RS256`, `RS384` and `RS512` verify; the `PS*` and
|
|
77
|
+
`ES*` keys throw `Invalid digest`.
|
|
78
|
+
- A detached signature that happens to decode as a JWT takes the JWT path.
|
|
79
|
+
- `JWKSAuthorization`: a denied raw-payload signature gives `false`, but an
|
|
80
|
+
expired JWT, an `issuer` or `audience` mismatch, a bad JWT signature or a
|
|
81
|
+
missing `kid`/`algorithm`/`signatureFormat` THROWS. On a callback each error
|
|
82
|
+
becomes a refusal.
|
|
83
|
+
- Always set `issuer` and `audience` for a JWT. Without them a token minted for
|
|
84
|
+
another service passes.
|
|
85
|
+
- Callback timestamps: all digits with 13 or more digits are milliseconds,
|
|
86
|
+
fewer are seconds. Other strings go through `Date.parse`.
|
|
87
|
+
- The package does not read HTTP headers or env, and has no OIDC discovery. For
|
|
88
|
+
a token from an OIDC provider, see **framework-oidc**.
|
|
89
|
+
|
|
90
|
+
## Test
|
|
91
|
+
|
|
92
|
+
The handler fetches keys over HTTP from `url`. Serve a JWKS from a local HTTP
|
|
93
|
+
server in the test (export the public key with
|
|
94
|
+
`publicKey.export({ format: 'jwk' })` and add `kid`), sign a token with
|
|
95
|
+
`jsonwebtoken` (`algorithm: 'RS256'`, `keyid`), and call the default export of
|
|
96
|
+
`@t4h.framework/jwks/authorize`.
|
|
97
|
+
|
|
98
|
+
## Related skills
|
|
99
|
+
|
|
100
|
+
- **framework-hmac**: shared-secret signatures.
|
|
101
|
+
- **framework-webhook**: wait for a partner callback that uses this `auth`.
|
|
102
|
+
|
|
103
|
+
## Reference
|
|
104
|
+
|
|
105
|
+
- Guide: <https://tech4humans-brasil.github.io/framework/jwks/>
|
|
106
|
+
- Types: `node_modules/@t4h.framework/jwks/dist/jwks.d.ts`
|
|
107
|
+
- Callback `auth` types: `AsyncActivityCallbackAuthJwt` and
|
|
108
|
+
`AsyncActivityCallbackAuthJwks` in `node_modules/@t4h.framework/core/dist/`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @t4h.framework/jwks
|
|
2
2
|
|
|
3
|
+
## 0.7.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#257](https://github.com/tech4humans-brasil/framework/pull/257) [`586bfb4`](https://github.com/tech4humans-brasil/framework/commit/586bfb4663f4a478527ee0c8db360603f034d615) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The jwks README is now short and links to the site. The site page now describes the `jwt` and `jwks` callback `auth`, the results and errors, and says that only the `RS*` keys verify a detached signature.
|
|
8
|
+
|
|
9
|
+
## 0.7.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#243](https://github.com/tech4humans-brasil/framework/pull/243) [`0ffd663`](https://github.com/tech4humans-brasil/framework/commit/0ffd663ce8369edd7b54206cb3216568ad1de471) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The README examples read each value from `environments`, an `EnvDesign` in `src/constants/environments.ts`, and pass each credential as a `Secret`. They no longer use `process.env`, the raw `env` from the core or a literal in a `Secret` field. The jwks README describes the real context fields (`signature`, `rawPayload`) and says that a token that `jwt.verify` rejects throws.
|
|
14
|
+
|
|
15
|
+
- [#240](https://github.com/tech4humans-brasil/framework/pull/240) [`0606115`](https://github.com/tech4humans-brasil/framework/commit/0606115e2e0baa994e9f0ed3559ee54ecac88e44) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The skills do not repeat a rule that a lint rule, `tsc` or a runtime error already enforces. Each such rule is now one line that names the gate. The examples that broke a gate (`process.env`, `as any`, an inline secret, an `EnvDesign` in a workflow file, `ref.output` before `wait()`) are correct now.
|
|
16
|
+
|
|
17
|
+
- [#228](https://github.com/tech4humans-brasil/framework/pull/228) [`d29e1ab`](https://github.com/tech4humans-brasil/framework/commit/d29e1ab29a039b13c3e4b075472bd615566ae75e) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - Uses exact types and `unknown` in place of `any`, and checks external data with guards.
|
|
18
|
+
- harness: the fix-issues prompt lists each sensor as `- checkTypes: PASS`. Before, it printed `- 0: [object Object]`.
|
|
19
|
+
- hmac: an algorithm name such as `constructor` throws `Unsupported algorithm`.
|
|
20
|
+
- oidc: a discovery response with no `introspection_endpoint`, or a null introspection body, throws a clear error.
|
|
21
|
+
- `HttpRequestActivity.toOutput` takes `HttpRequestActivityOutput` in place of `any`.
|
|
22
|
+
|
|
3
23
|
## 0.7.6
|
|
4
24
|
|
|
5
25
|
## 0.7.5
|
package/README.md
CHANGED
|
@@ -1,73 +1,31 @@
|
|
|
1
1
|
# @t4h.framework/jwks
|
|
2
2
|
|
|
3
|
-
JWKS
|
|
3
|
+
JWKS authorization for the T4H Framework. It verifies an RS256 JWT, or a
|
|
4
|
+
detached signature over a raw payload, against the public keys of a JWKS
|
|
5
|
+
endpoint, for a workflow, an app or the callbacks of an async activity.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Exports
|
|
10
|
-
|
|
11
|
-
| Entry | Purpose |
|
|
12
|
-
|------------------|---------|
|
|
13
|
-
| `@t4h.framework/jwks` | `JWKSAuthorization`, `JWKSAuthorizationContext`, and related types |
|
|
14
|
-
| `@t4h.framework/jwks/authorize` | Default async handler invoked by the runtime with the manifest context |
|
|
15
|
-
| `@t4h.framework/jwks/callback` | Default export `jwksCallback`: the `jwt` (bearer JWT) and `jwks` (detached signature verified with a JWKS key) callback authorizer for the core `AsyncActivityCallback.authorize` |
|
|
16
|
-
|
|
17
|
-
## `JWKSAuthorization`
|
|
18
|
-
|
|
19
|
-
Subclass of [`Authorization`](https://github.com/tech4humans-brasil/framework/tree/main/packages/core) from core. `toManifest()` points the runtime at `@t4h.framework/jwks/authorize` and passes a `JWKSAuthorizationContext`.
|
|
20
|
-
|
|
21
|
-
Construct it with a full `JWKSAuthorizationContext` (including `url` and `value`). How you obtain `value`—headers, query, env, or runtime injection—is up to your app and deployment pipeline; this package does not read HTTP or env by itself.
|
|
22
|
-
|
|
23
|
-
## Context: `JWKSAuthorizationContext`
|
|
24
|
-
|
|
25
|
-
| Field | Required | Description |
|
|
26
|
-
|-------|----------|-------------|
|
|
27
|
-
| `url` | Yes | JWKS document URL (`jwks-rsa` `jwksUri`). |
|
|
28
|
-
| `value` | Yes | Credential to verify (Bearer token, raw payload, etc., depending on your app). Empty string yields `false`. |
|
|
29
|
-
| `issuer` | No | If set, passed to JWT verification as expected `iss`. |
|
|
30
|
-
| `audience` | No | If set, passed to JWT verification as expected `aud`. |
|
|
31
|
-
| `kid` | Conditional | Required when `value` is **not** a decodable JWT (see below). |
|
|
32
|
-
| `algorithm` | Conditional | Same as `kid` for non-JWT `value`. |
|
|
33
|
-
| `signatureFormat` | Conditional | Same as `kid` for non-JWT `value` (encoding for the signature buffer, e.g. `hex` or `base64`). |
|
|
34
|
-
|
|
35
|
-
There is no built-in OIDC discovery (`JWKS_ISSUER`), no environment-variable mapping in this package, and no `credentialSource` helper—you choose where `value` comes from in your application or hosting layer.
|
|
36
|
-
|
|
37
|
-
## Runtime handler (`jwks/authorize`)
|
|
38
|
-
|
|
39
|
-
The default export is an async function:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
(options: JWKSAuthorizationContext) => Promise<boolean>
|
|
9
|
+
```bash
|
|
10
|
+
npm install @t4h.framework/jwks @t4h.framework/core
|
|
43
11
|
```
|
|
44
12
|
|
|
45
|
-
|
|
46
|
-
- Returns `true` if verification succeeds.
|
|
47
|
-
|
|
48
|
-
### JWT path
|
|
49
|
-
|
|
50
|
-
If `value` decodes as a JWT (`jsonwebtoken` `decode` with `complete: true` succeeds), verification uses `validateJwt` ([source](https://github.com/tech4humans-brasil/framework/blob/main/packages/jwks/src/helpers/validateJwt.ts)):
|
|
51
|
-
|
|
52
|
-
- Algorithm **RS256** only (`alg` in the header must be `RS256`).
|
|
53
|
-
- Header must include `kid` (used to fetch the signing key from JWKS).
|
|
54
|
-
- `exp` is enforced (token must not be expired).
|
|
55
|
-
- Optional `issuer` and `audience` from the context are applied when present.
|
|
56
|
-
|
|
57
|
-
### Non-JWT path
|
|
58
|
-
|
|
59
|
-
If `value` is not treated as a JWT by the decoder, the handler expects `kid`, `algorithm`, and `signatureFormat` on the context. If any of these are missing, it **throws** with a clear error. It loads the public key from JWKS by `kid` and verifies using Node’s `crypto.createVerify`.
|
|
60
|
-
|
|
61
|
-
## Example
|
|
13
|
+
## Usage
|
|
62
14
|
|
|
63
15
|
```typescript
|
|
64
16
|
import { JWKSAuthorization } from '@t4h.framework/jwks'
|
|
65
17
|
|
|
66
18
|
const authorization = new JWKSAuthorization({
|
|
67
19
|
url: 'https://auth.example.com/.well-known/jwks.json',
|
|
68
|
-
|
|
20
|
+
signature: '<jwt-from-the-request>',
|
|
21
|
+
rawPayload: '',
|
|
22
|
+
issuer: 'https://auth.example.com/',
|
|
69
23
|
audience: 'https://api.example.com',
|
|
70
24
|
})
|
|
71
25
|
```
|
|
72
26
|
|
|
73
|
-
|
|
27
|
+
Attach `authorization` to a `Workflow` or an `App`.
|
|
28
|
+
|
|
29
|
+
## Documentation
|
|
30
|
+
|
|
31
|
+
<https://tech4humans-brasil.github.io/framework/jwks/>
|
|
@@ -9,7 +9,7 @@ export async function validateJwt(token, client, options) {
|
|
|
9
9
|
return false;
|
|
10
10
|
const key = await client.getSigningKey(decoded.header.kid);
|
|
11
11
|
const publicKey = key.getPublicKey();
|
|
12
|
-
const isValid =
|
|
12
|
+
const isValid = jwt.verify(token, publicKey, {
|
|
13
13
|
algorithms: ['RS256'],
|
|
14
14
|
ignoreExpiration: false,
|
|
15
15
|
issuer: options?.issuer,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateJwt.js","sourceRoot":"","sources":["../../src/helpers/validateJwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAQ9B,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,MAAkB,EAClB,OAA4B;IAE5B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAErD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IAErC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAA;IAEhD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAE1D,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,EAAE,CAAA;IAEpC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"validateJwt.js","sourceRoot":"","sources":["../../src/helpers/validateJwt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,cAAc,CAAA;AAQ9B,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,MAAkB,EAClB,OAA4B;IAE5B,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAErD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IAErC,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,OAAO;QAAE,OAAO,KAAK,CAAA;IAEhD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAE1D,MAAM,SAAS,GAAG,GAAG,CAAC,YAAY,EAAE,CAAA;IAEpC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE;QAC3C,UAAU,EAAE,CAAC,OAAO,CAAC;QACrB,gBAAgB,EAAE,KAAK;QACvB,MAAM,EAAE,OAAO,EAAE,MAAM;QACvB,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAC,CAAA;IAEF,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAA;AACpC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks-callback.d.ts","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"jwks-callback.d.ts","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,6BAA6B,EAClC,KAAK,4BAA4B,EAClC,MAAM,qBAAqB,CAAA;AA+G5B,wBAA8B,YAAY,CACxC,IAAI,EAAE,4BAA4B,GAAG,6BAA6B,EAClE,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,GACvC,OAAO,CAAC,OAAO,CAAC,CAIlB"}
|
package/dist/jwks-callback.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { AsyncActivityCallback, } from '@t4h.framework/core';
|
|
2
2
|
import jwks from './jwks-authorize.js';
|
|
3
|
+
function toEpochMilliseconds(timestamp) {
|
|
4
|
+
if (!/^\d+$/.test(timestamp))
|
|
5
|
+
return Date.parse(timestamp);
|
|
6
|
+
const isAlreadyInMilliseconds = timestamp.length >= 13;
|
|
7
|
+
if (isAlreadyInMilliseconds)
|
|
8
|
+
return Number(timestamp);
|
|
9
|
+
return Number(timestamp) * 1000;
|
|
10
|
+
}
|
|
3
11
|
function isWithinTolerance(timestamp, toleranceInSeconds) {
|
|
4
12
|
const value = timestamp.trim();
|
|
5
13
|
if (!value)
|
|
6
14
|
return false;
|
|
7
|
-
const signedAt =
|
|
8
|
-
? value.length >= 13
|
|
9
|
-
? Number(value)
|
|
10
|
-
: Number(value) * 1000
|
|
11
|
-
: Date.parse(value);
|
|
15
|
+
const signedAt = toEpochMilliseconds(value);
|
|
12
16
|
if (Number.isNaN(signedAt))
|
|
13
17
|
return false;
|
|
14
18
|
return Math.abs(Date.now() - signedAt) / 1000 <= toleranceInSeconds;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks-callback.js","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAGtB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,IAAI,MAAM,qBAAqB,CAAA;AAEtC,SAAS,
|
|
1
|
+
{"version":3,"file":"jwks-callback.js","sourceRoot":"","sources":["../src/jwks-callback.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ export type JWKSAuthorizationContext = {
|
|
|
8
8
|
audience?: string;
|
|
9
9
|
kid?: string;
|
|
10
10
|
algorithm?: string;
|
|
11
|
-
signatureFormat?: BinaryToTextEncoding | string;
|
|
11
|
+
signatureFormat?: BinaryToTextEncoding | (string & {});
|
|
12
12
|
};
|
|
13
13
|
export type JWKSAuthorizationManifest = AuthorizationManifest<JWKSAuthorizationContext>;
|
|
14
14
|
export declare class JWKSAuthorization extends Authorization<JWKSAuthorizationContext> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JWKSAuthorization.d.ts","sourceRoot":"","sources":["../../src/models/JWKSAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"JWKSAuthorization.d.ts","sourceRoot":"","sources":["../../src/models/JWKSAuthorization.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,MAAM,MAAM,wBAAwB,GAAG;IACrC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,oBAAoB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,yBAAyB,GACnC,qBAAqB,CAAC,wBAAwB,CAAC,CAAA;AAEjD,qBAAa,iBAAkB,SAAQ,aAAa,CAAC,wBAAwB,CAAC;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,wBAAwB;IAIvD,UAAU,IAAI,yBAAyB;CAe/C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t4h.framework/jwks",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "JWKS module for the T4H Framework authorization",
|
|
5
5
|
"homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/jwks",
|
|
6
6
|
"bugs": "https://github.com/tech4humans-brasil/framework/issues",
|
|
@@ -36,12 +36,14 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc --project tsconfig.build.json",
|
|
38
38
|
"check-types": "tsc --noEmit",
|
|
39
|
+
"check-package": "publint --strict && attw --pack . --profile esm-only",
|
|
40
|
+
"lint": "eslint . --max-warnings 0",
|
|
39
41
|
"prepublishOnly": "yarn build",
|
|
40
42
|
"test": "vitest run",
|
|
41
43
|
"test:watch": "vitest"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
|
-
"@t4h.framework/core": "^0.
|
|
46
|
+
"@t4h.framework/core": "^0.21.0",
|
|
45
47
|
"@types/jsonwebtoken": "^9",
|
|
46
48
|
"typescript": "^5.9.3",
|
|
47
49
|
"vitest": "^4.0.18"
|