Skip to content

Commit cda4b53

Browse files
committed
feat: add JWKS Cache management for use in non-persistent runtimes
1 parent 2cd11f2 commit cda4b53

File tree

8 files changed

+250
-5
lines changed

8 files changed

+250
-5
lines changed

docs/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Support from the community to continue maintaining and improving this module is
3232
- [enableDecryptingResponses](functions/enableDecryptingResponses.md)
3333
- [enableDetachedSignatureResponseChecks](functions/enableDetachedSignatureResponseChecks.md)
3434
- [enableNonRepudiationChecks](functions/enableNonRepudiationChecks.md)
35+
- [getJwksCache](functions/getJwksCache.md)
36+
- [setJwksCache](functions/setJwksCache.md)
3537
- [useCodeIdTokenResponseType](functions/useCodeIdTokenResponseType.md)
3638
- [useJwtResponseMode](functions/useJwtResponseMode.md)
3739

@@ -103,9 +105,12 @@ Support from the community to continue maintaining and improving this module is
103105
- [DiscoveryRequestOptions](interfaces/DiscoveryRequestOptions.md)
104106
- [DPoPHandle](interfaces/DPoPHandle.md)
105107
- [DPoPOptions](interfaces/DPoPOptions.md)
108+
- [ExportedJWKSCache](interfaces/ExportedJWKSCache.md)
106109
- [GenerateKeyPairOptions](interfaces/GenerateKeyPairOptions.md)
107110
- [IDToken](interfaces/IDToken.md)
108111
- [IntrospectionResponse](interfaces/IntrospectionResponse.md)
112+
- [JWK](interfaces/JWK.md)
113+
- [JWKS](interfaces/JWKS.md)
109114
- [ModifyAssertionFunction](interfaces/ModifyAssertionFunction.md)
110115
- [ModifyAssertionOptions](interfaces/ModifyAssertionOptions.md)
111116
- [MTLSEndpointAliases](interfaces/MTLSEndpointAliases.md)

docs/functions/getJwksCache.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Function: getJwksCache()
2+
3+
[💗 Help the project](https://github.com/sponsors/panva)
4+
5+
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).
6+
7+
***
8+
9+
**getJwksCache**(`config`): [`ExportedJWKSCache`](../interfaces/ExportedJWKSCache.md) \| `undefined`
10+
11+
This function can be used to export the JSON Web Key Set and the timestamp at
12+
which it was last fetched if the client used the
13+
[authorization server's JWK Set](../interfaces/ServerMetadata.md#jwks_uri) to validate
14+
digital signatures.
15+
16+
This function is intended for cloud computing runtimes that cannot keep an in
17+
memory cache between their code's invocations. Use in runtimes where an in
18+
memory cache between requests is available is not desirable.
19+
20+
Note: the client only uses the authorization server's JWK Set when
21+
[enableNonRepudiationChecks](enableNonRepudiationChecks.md), [useJwtResponseMode](useJwtResponseMode.md), or
22+
[useCodeIdTokenResponseType](useCodeIdTokenResponseType.md) is used.
23+
24+
## Parameters
25+
26+
| Parameter | Type |
27+
| ------ | ------ |
28+
| `config` | [`Configuration`](../classes/Configuration.md) |
29+
30+
## Returns
31+
32+
[`ExportedJWKSCache`](../interfaces/ExportedJWKSCache.md) \| `undefined`

docs/functions/setJwksCache.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Function: setJwksCache()
2+
3+
[💗 Help the project](https://github.com/sponsors/panva)
4+
5+
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).
6+
7+
***
8+
9+
**setJwksCache**(`config`, `jwksCache`): `void`
10+
11+
DANGER ZONE - Use of this function has security implications that must be
12+
understood, assessed for applicability, and accepted before use. It is
13+
critical that the JSON Web Key Set cache only be writable by your own code.
14+
15+
This option is intended for cloud computing runtimes that cannot keep an in
16+
memory cache between their code's invocations. Use in runtimes where an in
17+
memory cache between requests is available is not desirable.
18+
19+
## Parameters
20+
21+
| Parameter | Type | Description |
22+
| ------ | ------ | ------ |
23+
| `config` | [`Configuration`](../classes/Configuration.md) | - |
24+
| `jwksCache` | [`ExportedJWKSCache`](../interfaces/ExportedJWKSCache.md) | JWKS Cache previously obtained from [getJwksCache](getJwksCache.md) |
25+
26+
## Returns
27+
28+
`void`

docs/interfaces/ExportedJWKSCache.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Interface: ExportedJWKSCache
2+
3+
[💗 Help the project](https://github.com/sponsors/panva)
4+
5+
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).
6+
7+
***
8+
9+
## Properties
10+
11+
### jwks
12+
13+
**jwks**: [`JWKS`](JWKS.md)
14+
15+
***
16+
17+
### uat
18+
19+
**uat**: `number`

docs/interfaces/JWK.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Interface: JWK
2+
3+
[💗 Help the project](https://github.com/sponsors/panva)
4+
5+
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).
6+
7+
***
8+
9+
## Indexable
10+
11+
\[`parameter`: `string`\]: [`JsonValue`](../type-aliases/JsonValue.md) \| `undefined`
12+
13+
## Properties
14+
15+
### alg?
16+
17+
`readonly` `optional` **alg**: `string`
18+
19+
***
20+
21+
### crv?
22+
23+
`readonly` `optional` **crv**: `string`
24+
25+
***
26+
27+
### e?
28+
29+
`readonly` `optional` **e**: `string`
30+
31+
***
32+
33+
### key\_ops?
34+
35+
`readonly` `optional` **key\_ops**: `string`[]
36+
37+
***
38+
39+
### kid?
40+
41+
`readonly` `optional` **kid**: `string`
42+
43+
***
44+
45+
### kty?
46+
47+
`readonly` `optional` **kty**: `string`
48+
49+
***
50+
51+
### n?
52+
53+
`readonly` `optional` **n**: `string`
54+
55+
***
56+
57+
### use?
58+
59+
`readonly` `optional` **use**: `string`
60+
61+
***
62+
63+
### x?
64+
65+
`readonly` `optional` **x**: `string`
66+
67+
***
68+
69+
### y?
70+
71+
`readonly` `optional` **y**: `string`

docs/interfaces/JWKS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Interface: JWKS
2+
3+
[💗 Help the project](https://github.com/sponsors/panva)
4+
5+
Support from the community to continue maintaining and improving this module is welcome. If you find the module useful, please consider supporting the project by [becoming a sponsor](https://github.com/sponsors/panva).
6+
7+
***
8+
9+
## Properties
10+
11+
### keys
12+
13+
`readonly` **keys**: [`JWK`](JWK.md)[]

src/index.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ interface Internal {
3939
hybrid?: HybridImplementation
4040
nonRepudiation?: NonRepudiationImplementation
4141
decrypt?: oauth.JweDecryptFunction
42+
jwksCache: oauth.JWKSCacheInput
4243
}
4344

4445
const int = (config: Configuration) => {
@@ -57,13 +58,16 @@ export {
5758
type AuthorizationDetails,
5859
type ConfirmationClaims,
5960
type DeviceAuthorizationResponse,
61+
type ExportedJWKSCache,
6062
type GenerateKeyPairOptions,
6163
type IDToken,
6264
type IntrospectionResponse,
6365
type JsonArray,
6466
type JsonObject,
6567
type JsonPrimitive,
6668
type JsonValue,
69+
type JWK,
70+
type JWKS,
6771
type JWSAlgorithm,
6872
type ModifyAssertionFunction,
6973
type ModifyAssertionOptions,
@@ -1640,6 +1644,7 @@ export class Configuration
16401644
c,
16411645
auth,
16421646
tlsOnly: true,
1647+
jwksCache: {},
16431648
})
16441649
}
16451650

@@ -2060,6 +2065,52 @@ export function allowInsecureRequests(config: Configuration) {
20602065
int(config).tlsOnly = false
20612066
}
20622067

2068+
/**
2069+
* DANGER ZONE - Use of this function has security implications that must be
2070+
* understood, assessed for applicability, and accepted before use. It is
2071+
* critical that the JSON Web Key Set cache only be writable by your own code.
2072+
*
2073+
* This option is intended for cloud computing runtimes that cannot keep an in
2074+
* memory cache between their code's invocations. Use in runtimes where an in
2075+
* memory cache between requests is available is not desirable.
2076+
*
2077+
* @param jwksCache JWKS Cache previously obtained from {@link getJwksCache}
2078+
*
2079+
* @group Advanced Configuration
2080+
*/
2081+
export function setJwksCache(
2082+
config: Configuration,
2083+
jwksCache: oauth.ExportedJWKSCache,
2084+
) {
2085+
int(config).jwksCache = structuredClone(jwksCache)
2086+
}
2087+
2088+
/**
2089+
* This function can be used to export the JSON Web Key Set and the timestamp at
2090+
* which it was last fetched if the client used the
2091+
* {@link ServerMetadata.jwks_uri authorization server's JWK Set} to validate
2092+
* digital signatures.
2093+
*
2094+
* This function is intended for cloud computing runtimes that cannot keep an in
2095+
* memory cache between their code's invocations. Use in runtimes where an in
2096+
* memory cache between requests is available is not desirable.
2097+
*
2098+
* Note: the client only uses the authorization server's JWK Set when
2099+
* {@link enableNonRepudiationChecks}, {@link useJwtResponseMode}, or
2100+
* {@link useCodeIdTokenResponseType} is used.
2101+
*
2102+
* @group Advanced Configuration
2103+
*/
2104+
export function getJwksCache(
2105+
config: Configuration,
2106+
): oauth.ExportedJWKSCache | undefined {
2107+
const cache = int(config).jwksCache
2108+
if (cache.uat) {
2109+
return cache as oauth.ExportedJWKSCache
2110+
}
2111+
return undefined
2112+
}
2113+
20632114
/**
20642115
* Enables validating the JWS Signature of either a JWT {@link !Response.body} or
20652116
* {@link TokenEndpointResponse.id_token} of a processed {@link !Response} such as
@@ -2116,13 +2167,14 @@ export function enableNonRepudiationChecks(config: Configuration) {
21162167
checkConfig(config)
21172168

21182169
int(config).nonRepudiation = (response) => {
2119-
const { as, fetch, tlsOnly, timeout } = int(config)
2170+
const { as, fetch, tlsOnly, timeout, jwksCache } = int(config)
21202171
return oauth
21212172
.validateApplicationLevelSignature(as, response, {
21222173
[oauth.customFetch]: fetch,
21232174
[oauth.allowInsecureRequests]: !tlsOnly,
21242175
headers: new Headers(headers),
21252176
signal: signal(timeout),
2177+
[oauth.jwksCache]: jwksCache,
21262178
})
21272179
.catch(errorHandler)
21282180
}
@@ -2565,14 +2617,15 @@ async function validateJARMResponse(
25652617
authorizationResponse: URL,
25662618
expectedState: string | typeof skipStateCheck | undefined,
25672619
): Promise<URLSearchParams> {
2568-
const { as, c, fetch, tlsOnly, timeout, decrypt } = int(config)
2620+
const { as, c, fetch, tlsOnly, timeout, decrypt, jwksCache } = int(config)
25692621
return oauth
25702622
.validateJwtAuthResponse(as, c, authorizationResponse, expectedState, {
25712623
[oauth.customFetch]: fetch,
25722624
[oauth.allowInsecureRequests]: !tlsOnly,
25732625
headers: new Headers(headers),
25742626
signal: signal(timeout),
25752627
[oauth.jweDecrypt]: decrypt,
2628+
[oauth.jwksCache]: jwksCache,
25762629
})
25772630
.catch(errorHandler)
25782631
}
@@ -2599,7 +2652,7 @@ async function validateCodeIdTokenResponse(
25992652
)
26002653
}
26012654

2602-
const { as, c, fetch, tlsOnly, timeout, decrypt } = int(config)
2655+
const { as, c, fetch, tlsOnly, timeout, decrypt, jwksCache } = int(config)
26032656

26042657
return (
26052658
fapi
@@ -2611,6 +2664,7 @@ async function validateCodeIdTokenResponse(
26112664
headers: new Headers(headers),
26122665
signal: signal(timeout),
26132666
[oauth.jweDecrypt]: decrypt,
2667+
[oauth.jwksCache]: jwksCache,
26142668
}).catch(errorHandler)
26152669
}
26162670

tap/end2end.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default (QUnit: QUnit) => {
1818
| 'dpop'
1919
| 'jwtUserinfo'
2020
| 'hybrid'
21+
| 'nonrepudiation'
2122
| 'encryption'
2223
| 'login'
2324
>
@@ -31,6 +32,7 @@ export default (QUnit: QUnit) => {
3132
hybrid: false,
3233
login: false,
3334
encryption: false,
35+
nonrepudiation: false,
3436
}
3537
for (const flag of flags) {
3638
conf[flag] = true
@@ -40,6 +42,8 @@ export default (QUnit: QUnit) => {
4042

4143
const testCases = [
4244
options(),
45+
options('nonrepudiation'),
46+
options('nonrepudiation', 'encryption'),
4347
options('par'),
4448
options('jar'),
4549
options('dpop'),
@@ -55,7 +59,16 @@ export default (QUnit: QUnit) => {
5559
]
5660

5761
for (const config of testCases) {
58-
const { jarm, par, jar, dpop, jwtUserinfo, hybrid, encryption } = config
62+
const {
63+
jarm,
64+
par,
65+
jar,
66+
dpop,
67+
jwtUserinfo,
68+
hybrid,
69+
encryption,
70+
nonrepudiation,
71+
} = config
5972

6073
function label(config: Record<string, boolean>) {
6174
const keys = Object.keys(
@@ -89,10 +102,12 @@ export default (QUnit: QUnit) => {
89102
)
90103

91104
const execute: Array<(config: lib.Configuration) => void> = [
92-
lib.enableNonRepudiationChecks,
93105
lib.allowInsecureRequests,
94106
]
95107

108+
if (nonrepudiation) {
109+
execute.push(lib.enableNonRepudiationChecks)
110+
}
96111
if (jarm) {
97112
execute.push(lib.useJwtResponseMode)
98113
}
@@ -222,6 +237,14 @@ export default (QUnit: QUnit) => {
222237
DPoP,
223238
})
224239

240+
if (jarm || hybrid || nonrepudiation) {
241+
const cache = lib.getJwksCache(client)
242+
t.ok(cache?.uat)
243+
t.ok(cache?.jwks)
244+
} else {
245+
t.notOk(lib.getJwksCache(client))
246+
}
247+
225248
t.ok(1)
226249
})
227250
}

0 commit comments

Comments
 (0)