feat: support response_type=id_token OIDC Authentication Responses

This commit is contained in:
Filip Skokan
2025-05-06 18:47:25 +02:00
parent 68e83694d0
commit 9682dc4974
13 changed files with 588 additions and 89 deletions
+9
View File
@@ -33,11 +33,20 @@ jobs:
variant:
client_registration: dynamic_client
# Core 1.0 response_type=id_token
- plan: oidcc-client-implicit-certification-test-plan
- plan: oidcc-client-implicit-certification-test-plan
variant:
client_registration: dynamic_client
# Comprehensive Core 1.0 client test
- plan: oidcc-client-test-plan
- plan: oidcc-client-test-plan
variant:
response_type: code id_token
- plan: oidcc-client-test-plan
variant:
response_type: id_token
# Financial-grade API Security Profile 1.0 - Part 2: Advanced
- plan: fapi1-advanced-final-client-test-plan
+5
View File
@@ -19,6 +19,7 @@ const {
switch (PLAN_NAME) {
case 'oidcc-client-basic-certification-test-plan':
case 'oidcc-client-implicit-certification-test-plan':
case 'oidcc-client-test-plan':
case 'oidcc-client-hybrid-certification-test-plan':
case 'fapi1-advanced-final-client-test-plan':
@@ -68,6 +69,10 @@ const DEFAULTS: Record<typeof PLAN_NAME, Record<string, string>> = {
request_type: 'plain_http_request',
client_registration: 'static_client', // dynamic_client
},
'oidcc-client-implicit-certification-test-plan': {
request_type: 'plain_http_request',
client_registration: 'static_client', // dynamic_client
},
'oidcc-client-hybrid-certification-test-plan': {
request_type: 'plain_http_request',
client_registration: 'static_client', // dynamic_client
+58 -38
View File
@@ -59,6 +59,7 @@ switch (plan.name) {
break
case 'oidcc-client-test-plan':
case 'oidcc-client-basic-certification-test-plan':
case 'oidcc-client-implicit-certification-test-plan':
case 'oidcc-client-hybrid-certification-test-plan':
prefix = 'oidcc-client-test-'
break
@@ -287,6 +288,10 @@ export const flow = (options?: MacroOptions) => {
}
}
if (response_type === 'id_token') {
execute.push(client.useIdTokenResponseType)
}
const [jwk] = configuration.client.jwks.keys
const clientPrivateKey = {
kid: jwk.kid,
@@ -472,54 +477,69 @@ export const flow = (options?: MacroOptions) => {
t.log('response redirect to', currentUrl.href)
const response = await client.authorizationCodeGrant(
config,
input,
{
expectedNonce: nonce,
expectedState: state,
pkceCodeVerifier: code_verifier,
},
undefined,
{ DPoP },
)
t.log('token endpoint response', { ...response })
if (
!plan.name.startsWith('fapi1') &&
scope.includes('openid') &&
config.serverMetadata().userinfo_endpoint
) {
// fetch userinfo response
t.log('fetching', config.serverMetadata().userinfo_endpoint)
const userinfo = await client.fetchUserInfo(
if (response_type === 'id_token') {
const response = await client.implicitAuthentication(
config,
response.access_token,
response.claims()?.sub!,
currentUrl,
nonce!,
{
DPoP,
expectedState: state,
},
)
t.log('userinfo endpoint response', { ...userinfo })
}
if (accounts_endpoint) {
const resource = await client.fetchProtectedResource(
t.log('validated ID Token Claims Set', {
...response,
})
} else {
const response = await client.authorizationCodeGrant(
config,
response.access_token,
new URL(accounts_endpoint),
'GET',
null,
input,
{
expectedNonce: nonce,
expectedState: state,
pkceCodeVerifier: code_verifier,
},
undefined,
{ DPoP },
)
const result = await resource.text()
try {
t.log('accounts endpoint response', JSON.parse(result))
} catch {
t.log('accounts endpoint response body', result)
t.log('token endpoint response', { ...response })
if (
!plan.name.startsWith('fapi1') &&
scope.includes('openid') &&
config.serverMetadata().userinfo_endpoint
) {
// fetch userinfo response
t.log('fetching', config.serverMetadata().userinfo_endpoint)
const userinfo = await client.fetchUserInfo(
config,
response.access_token,
response.claims()?.sub!,
{
DPoP,
},
)
t.log('userinfo endpoint response', { ...userinfo })
}
if (accounts_endpoint) {
const resource = await client.fetchProtectedResource(
config,
response.access_token,
new URL(accounts_endpoint),
'GET',
null,
undefined,
{ DPoP },
)
const result = await resource.text()
try {
t.log('accounts endpoint response', JSON.parse(result))
} catch {
t.log('accounts endpoint response body', result)
}
}
}
+3
View File
@@ -38,6 +38,7 @@ Support from the community to continue maintaining and improving this module is
- [getJwksCache](functions/getJwksCache.md)
- [setJwksCache](functions/setJwksCache.md)
- [useCodeIdTokenResponseType](functions/useCodeIdTokenResponseType.md)
- [useIdTokenResponseType](functions/useIdTokenResponseType.md)
- [useJwtResponseMode](functions/useJwtResponseMode.md)
## Client Authentication Methods
@@ -80,6 +81,7 @@ Support from the community to continue maintaining and improving this module is
- [buildEndSessionUrl](functions/buildEndSessionUrl.md)
- [discovery](functions/discovery.md)
- [fetchUserInfo](functions/fetchUserInfo.md)
- [implicitAuthentication](functions/implicitAuthentication.md)
## PKCE
@@ -119,6 +121,7 @@ Support from the community to continue maintaining and improving this module is
- [ExportedJWKSCache](interfaces/ExportedJWKSCache.md)
- [GenerateKeyPairOptions](interfaces/GenerateKeyPairOptions.md)
- [IDToken](interfaces/IDToken.md)
- [ImplicitAuthenticationResponseChecks](interfaces/ImplicitAuthenticationResponseChecks.md)
- [IntrospectionResponse](interfaces/IntrospectionResponse.md)
- [JWK](interfaces/JWK.md)
- [JWKS](interfaces/JWKS.md)
+3
View File
@@ -15,6 +15,9 @@ Note:
[URL of the authorization server's authorization endpoint](../interfaces/ServerMetadata.md#authorization_endpoint)
must be configured.
Note: When used, PKCE code challenge, state, and nonce parameter values must
always be random and be tied to the user-agent.
## Parameters
| Parameter | Type | Description |
+2 -2
View File
@@ -18,8 +18,8 @@ memory cache between their code's invocations. Use in runtimes where an in
memory cache between requests is available is not desirable.
Note: the client only uses the authorization server's JWK Set when
[enableNonRepudiationChecks](enableNonRepudiationChecks.md), [useJwtResponseMode](useJwtResponseMode.md), or
[useCodeIdTokenResponseType](useCodeIdTokenResponseType.md) is used.
[enableNonRepudiationChecks](enableNonRepudiationChecks.md), [useJwtResponseMode](useJwtResponseMode.md),
[useCodeIdTokenResponseType](useCodeIdTokenResponseType.md), or [useIdTokenResponseType](useIdTokenResponseType.md) is used.
## Parameters
+83
View File
@@ -0,0 +1,83 @@
# Function: implicitAuthentication()
[💗 Help the project](https://github.com/sponsors/panva)
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).
***
▸ **implicitAuthentication**(`config`, `currentUrl`, `expectedNonce`, `checks`?): [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`IDToken`](../interfaces/IDToken.md)\>
This method validates the authorization server's
[Implicit Authentication Flow](https://openid.net/specs/openid-connect-core-1_0-errata2.html#ImplicitFlowAuth)
Response.
Note:
[URL of the authorization server's JWK Set document](../interfaces/ServerMetadata.md#jwks_uri)
must be configured.
Note: Only `response_type=id_token` responses are supported and prior use of
[useIdTokenResponseType](useIdTokenResponseType.md) is required.
## Parameters
| Parameter | Type | Description |
| ------ | ------ | ------ |
| `config` | [`Configuration`](../classes/Configuration.md) | - |
| `currentUrl` | [`URL`](https://developer.mozilla.org/docs/Web/API/URL) \| [`Request`](https://developer.mozilla.org/docs/Web/API/Request) | Current [URL](https://developer.mozilla.org/docs/Web/API/URL) the Authorization Server provided an Authorization Response to or a [Request](https://developer.mozilla.org/docs/Web/API/Request), the [Authentication Response Parameters](https://openid.net/specs/openid-connect-core-1_0-errata2.html#ImplicitAuthResponse) are extracted from this. |
| `expectedNonce` | `string` | Expected value of the `nonce` ID Token claim. This value must match exactly. |
| `checks`? | [`ImplicitAuthenticationResponseChecks`](../interfaces/ImplicitAuthenticationResponseChecks.md) | Additional optional Implicit Authentication Response checks |
## Returns
[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`IDToken`](../interfaces/IDToken.md)\>
ID Token Claims Set
## Examples
In a browser
```ts
let config!: client.Configuration
let expectedNonce!: string
let idTokenClaims = await client.implicitAuthentication(
config,
new URL(location.href),
expectedNonce,
)
```
On a server in response to a `form_post` response mode
```ts
let config!: client.Configuration
let expectedNonce!: string
let getCurrentUrl!: (...args: any) => URL
let getBody!: (...args: any) => Record<string, string>
let url = getCurrentUrl()
url.hash = new URLSearchParams(getBody()).toString()
let idTokenClaims = await client.implicitAuthentication(
config,
url,
expectedNonce,
)
```
On a server in response to a `form_post` response mode using an incoming
[Request](https://developer.mozilla.org/docs/Web/API/Request) object
```ts
let config!: client.Configuration
let expectedNonce!: string
let request!: Request
let idTokenClaims = await client.implicitAuthentication(
config,
request,
expectedNonce,
)
```
+62
View File
@@ -0,0 +1,62 @@
# Function: useIdTokenResponseType()
[💗 Help the project](https://github.com/sponsors/panva)
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).
***
▸ **useIdTokenResponseType**(`config`): `void`
This changes the `response_type` used by the client to be `id_token`, this
subsequently requires that the authorization server response be passed to
[implicitAuthentication](implicitAuthentication.md) (instead of [authorizationCodeGrant](authorizationCodeGrant.md)) and
for it to be one described by
[OpenID Connect 1.0 Implicit Flow](https://openid.net/specs/openid-connect-core-1_0-errata2.html#ImplicitFlowAuth).
Note:
[URL of the authorization server's JWK Set document](../interfaces/ServerMetadata.md#jwks_uri)
must be configured.
## Parameters
| Parameter | Type |
| ------ | ------ |
| `config` | [`Configuration`](../classes/Configuration.md) |
## Returns
`void`
## Examples
Usage with a [Configuration](../classes/Configuration.md) obtained through [discovery](discovery.md)
```ts
let server!: URL
let clientId!: string
let clientMetadata!: Partial<client.ClientMetadata> | undefined
let clientAuth = client.None()
let config = await client.discovery(
server,
clientId,
clientMetadata,
clientAuth,
{
execute: [client.useIdTokenResponseType],
},
)
```
Usage with a [Configuration](../classes/Configuration.md) instance
```ts
let config!: client.Configuration
client.useIdTokenResponseType(config)
```
## See
[OpenID Connect 1.0 Hybrid Flow](https://openid.net/specs/openid-connect-core-1_0-errata2.html#HybridFlowAuth)
@@ -99,6 +99,7 @@ let config = await client.discovery(
- [allowInsecureRequests](../functions/allowInsecureRequests.md)
- [enableNonRepudiationChecks](../functions/enableNonRepudiationChecks.md)
- [useCodeIdTokenResponseType](../functions/useCodeIdTokenResponseType.md)
- [useIdTokenResponseType](../functions/useIdTokenResponseType.md)
- [enableDetachedSignatureResponseChecks](../functions/enableDetachedSignatureResponseChecks.md)
- [useJwtResponseMode](../functions/useJwtResponseMode.md)
@@ -112,6 +112,7 @@ let config = await client.discovery(
- [allowInsecureRequests](../functions/allowInsecureRequests.md)
- [enableNonRepudiationChecks](../functions/enableNonRepudiationChecks.md)
- [useCodeIdTokenResponseType](../functions/useCodeIdTokenResponseType.md)
- [useIdTokenResponseType](../functions/useIdTokenResponseType.md)
- [enableDetachedSignatureResponseChecks](../functions/enableDetachedSignatureResponseChecks.md)
- [useJwtResponseMode](../functions/useJwtResponseMode.md)
@@ -0,0 +1,29 @@
# Interface: ImplicitAuthenticationResponseChecks
[💗 Help the project](https://github.com/sponsors/panva)
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).
***
## Properties
### expectedState?
• `optional` **expectedState**: `string` \| *typeof* `skipStateCheck`
Expected value of the `state` authorization response parameter. This value
must match exactly. When `undefined` the expectation is that there is no
`state` in the authorization response.
***
### maxAge?
• `optional` **maxAge**: `number`
ID Token [\`auth\_time\`](IDToken.md#auth_time) claim value will be checked
to be present and conform to this `maxAge` value. Use of this option is
required if you sent a `max_age` parameter in the authorization request.
Default is [ClientMetadata.default\_max\_age](ClientMetadata.md#default_max_age) and falls back to not
checking the claim's value beyond it being a number when present.
+285 -16
View File
@@ -40,6 +40,7 @@ interface Internal {
nonRepudiation?: NonRepudiationImplementation
decrypt?: oauth.JweDecryptFunction
jwksCache: oauth.JWKSCacheInput
implicit?: boolean
}
const int = (config: Configuration) => {
@@ -1061,6 +1062,7 @@ export interface DiscoveryRequestOptions {
* @see {@link allowInsecureRequests}
* @see {@link enableNonRepudiationChecks}
* @see {@link useCodeIdTokenResponseType}
* @see {@link useIdTokenResponseType}
* @see {@link enableDetachedSignatureResponseChecks}
* @see {@link useJwtResponseMode}
*/
@@ -2556,8 +2558,8 @@ export function setJwksCache(
* memory cache between requests is available is not desirable.
*
* Note: the client only uses the authorization server's JWK Set when
* {@link enableNonRepudiationChecks}, {@link useJwtResponseMode}, or
* {@link useCodeIdTokenResponseType} is used.
* {@link enableNonRepudiationChecks}, {@link useJwtResponseMode},
* {@link useCodeIdTokenResponseType}, or {@link useIdTokenResponseType} is used.
*
* @group Advanced Configuration
*/
@@ -2687,9 +2689,11 @@ export function enableNonRepudiationChecks(config: Configuration) {
export function useJwtResponseMode(config: Configuration) {
checkConfig(config)
if (int(config).hybrid) {
const { hybrid, implicit } = int(config)
if (hybrid || implicit) {
throw e(
'JARM cannot be combined with a hybrid response mode',
'JARM cannot be combined with a hybrid or implicit response types',
undefined,
oauth.UNSUPPORTED_OPERATION,
)
@@ -2768,6 +2772,191 @@ export function enableDetachedSignatureResponseChecks(config: Configuration) {
)
}
export interface ImplicitAuthenticationResponseChecks
extends Pick<AuthorizationCodeGrantChecks, 'expectedState' | 'maxAge'> {}
/**
* This method validates the authorization server's
* {@link https://openid.net/specs/openid-connect-core-1_0-errata2.html#ImplicitFlowAuth Implicit Authentication Flow}
* Response.
*
* Note:
* {@link ServerMetadata.jwks_uri URL of the authorization server's JWK Set document}
* must be configured.
*
* Note: Only `response_type=id_token` responses are supported and prior use of
* {@link useIdTokenResponseType} is required.
*
* @example
*
* In a browser
*
* ```ts
* let config!: client.Configuration
* let expectedNonce!: string
*
* let idTokenClaims = await client.implicitAuthentication(
* config,
* new URL(location.href),
* expectedNonce,
* )
* ```
*
* @example
*
* On a server in response to a `form_post` response mode
*
* ```ts
* let config!: client.Configuration
* let expectedNonce!: string
* let getCurrentUrl!: (...args: any) => URL
* let getBody!: (...args: any) => Record<string, string>
*
* let url = getCurrentUrl()
* url.hash = new URLSearchParams(getBody()).toString()
*
* let idTokenClaims = await client.implicitAuthentication(
* config,
* url,
* expectedNonce,
* )
* ```
*
* @example
*
* On a server in response to a `form_post` response mode using an incoming
* {@link !Request} object
*
* ```ts
* let config!: client.Configuration
* let expectedNonce!: string
* let request!: Request
*
* let idTokenClaims = await client.implicitAuthentication(
* config,
* request,
* expectedNonce,
* )
* ```
*
* @param currentUrl Current {@link !URL} the Authorization Server provided an
* Authorization Response to or a {@link !Request}, the
* {@link https://openid.net/specs/openid-connect-core-1_0-errata2.html#ImplicitAuthResponse Authentication Response Parameters}
* are extracted from this.
* @param expectedNonce Expected value of the `nonce` ID Token claim. This value
* must match exactly.
* @param checks Additional optional Implicit Authentication Response checks
*
* @returns End-User Claims from the ID Token.
*
* @group OpenID Connect 1.0
*/
export async function implicitAuthentication(
config: Configuration,
currentUrl: URL | Request,
expectedNonce: string,
checks?: ImplicitAuthenticationResponseChecks,
): Promise<oauth.IDToken> {
checkConfig(config)
if (
!(currentUrl instanceof URL) &&
!webInstanceOf<Request>(currentUrl, 'Request')
) {
throw CodedTypeError(
'"currentUrl" must be an instance of URL, or Request',
ERR_INVALID_ARG_TYPE,
)
}
if (typeof expectedNonce !== 'string') {
throw CodedTypeError(
'"expectedNonce" must be a string',
ERR_INVALID_ARG_TYPE,
)
}
const { as, c, fetch, tlsOnly, timeout, decrypt, implicit, jwksCache } =
int(config)
if (!implicit) {
throw new TypeError(
'implicitAuthentication() cannot be used by clients using flows other than response_type=id_token',
)
}
let params: URLSearchParams
if (!(currentUrl instanceof URL)) {
const request: Request = currentUrl
switch (request.method) {
case 'GET':
params = new URLSearchParams(new URL(request.url).hash.slice(1))
break
case 'POST':
// @ts-expect-error
params = new URLSearchParams(await oauth.formPostResponse(request))
break
default:
throw CodedTypeError(
'unexpected Request HTTP method',
ERR_INVALID_ARG_VALUE,
)
}
} else {
params = new URLSearchParams(currentUrl.hash.slice(1))
}
try {
// validate state, iss, error codes etc
{
const decoy = new URLSearchParams(params)
decoy.delete('id_token')
oauth.validateAuthResponse(
{
...as,
authorization_response_iss_parameter_supported: undefined,
},
c,
decoy,
checks?.expectedState,
)
}
// validate id_token
{
const decoy = new Response(
JSON.stringify({
access_token: 'decoy',
token_type: 'bearer',
id_token: params.get('id_token'),
}),
{
headers: new Headers({ 'content-type': 'application/json' }),
},
)
const ref = await oauth.processAuthorizationCodeResponse(as, c, decoy, {
expectedNonce,
maxAge: checks?.maxAge,
[oauth.jweDecrypt]: decrypt,
})
// validate ID Token signature
await oauth.validateApplicationLevelSignature(as, decoy, {
[oauth.customFetch]: fetch,
[oauth.allowInsecureRequests]: !tlsOnly,
headers: new Headers(headers),
signal: signal(timeout),
[oauth.jwksCache]: jwksCache,
})
return oauth.getValidatedIdTokenClaims(ref)!
}
} catch (err) {
errorHandler(err)
}
}
/**
* This changes the `response_type` used by the client to be `code id_token` and
* expects the authorization server response passed to
@@ -2816,9 +3005,11 @@ export function enableDetachedSignatureResponseChecks(config: Configuration) {
export function useCodeIdTokenResponseType(config: Configuration) {
checkConfig(config)
if (int(config).jarm) {
const { jarm, implicit } = int(config)
if (jarm || implicit) {
throw e(
'"code id_token" response type cannot be combined with JARM',
'"code id_token" response type cannot be combined with JARM or implicit response type',
undefined,
oauth.UNSUPPORTED_OPERATION,
)
@@ -2840,6 +3031,68 @@ export function useCodeIdTokenResponseType(config: Configuration) {
)
}
/**
* This changes the `response_type` used by the client to be `id_token`, this
* subsequently requires that the authorization server response be passed to
* {@link implicitAuthentication} (instead of {@link authorizationCodeGrant}) and
* for it to be one described by
* {@link https://openid.net/specs/openid-connect-core-1_0-errata2.html#ImplicitFlowAuth OpenID Connect 1.0 Implicit Flow}.
*
* Note:
* {@link ServerMetadata.jwks_uri URL of the authorization server's JWK Set document}
* must be configured.
*
* @example
*
* Usage with a {@link Configuration} obtained through {@link discovery}
*
* ```ts
* let server!: URL
* let clientId!: string
* let clientMetadata!: Partial<client.ClientMetadata> | undefined
* let clientAuth = client.None()
*
* let config = await client.discovery(
* server,
* clientId,
* clientMetadata,
* clientAuth,
* {
* execute: [client.useIdTokenResponseType],
* },
* )
* ```
*
* @example
*
* Usage with a {@link Configuration} instance
*
* ```ts
* let config!: client.Configuration
*
* client.useIdTokenResponseType(config)
* ```
*
* @group Advanced Configuration
*
* @see {@link https://openid.net/specs/openid-connect-core-1_0-errata2.html#HybridFlowAuth OpenID Connect 1.0 Hybrid Flow}
*/
export function useIdTokenResponseType(config: Configuration) {
checkConfig(config)
const { jarm, hybrid } = int(config)
if (jarm || hybrid) {
throw e(
'"id_token" response type cannot be combined with JARM or hybrid response type',
undefined,
oauth.UNSUPPORTED_OPERATION,
)
}
int(config).implicit = true
}
export interface AuthorizationCodeGrantChecks {
/**
* Expected value of the `nonce` ID Token claim. This value must match
@@ -2968,7 +3221,13 @@ export async function authorizationCodeGrant(
let redirectUri: string
const { as, c, auth, fetch, tlsOnly, jarm, hybrid, nonRepudiation, timeout, decrypt } = int(config) // prettier-ignore
const { as, c, auth, fetch, tlsOnly, jarm, hybrid, nonRepudiation, timeout, decrypt, implicit } = int(config) // prettier-ignore
if (implicit) {
throw new TypeError(
'authorizationCodeGrant() cannot be used by response_type=id_token clients',
)
}
if (options?.flag === retry) {
authResponse = options.authResponse!
@@ -3004,7 +3263,7 @@ export async function authorizationCodeGrant(
checks?.expectedState,
)
} catch (err) {
return errorHandler(err)
errorHandler(err)
}
}
}
@@ -3295,6 +3554,9 @@ export async function clientCredentialsGrant(
* {@link ServerMetadata.authorization_endpoint URL of the authorization server's authorization endpoint}
* must be configured.
*
* Note: When used, PKCE code challenge, state, and nonce parameter values must
* always be random and be tied to the user-agent.
*
* @example
*
* ```ts
@@ -3332,7 +3594,7 @@ export function buildAuthorizationUrl(
): URL {
checkConfig(config)
const { as, c, tlsOnly, hybrid, jarm } = int(config)
const { as, c, tlsOnly, hybrid, jarm, implicit } = int(config)
// @ts-expect-error
const authorizationEndpoint = oauth.resolveEndpoint(
as,
@@ -3348,7 +3610,16 @@ export function buildAuthorizationUrl(
}
if (!parameters.has('request_uri') && !parameters.has('request')) {
if (!parameters.has('response_type')) {
parameters.set('response_type', hybrid ? 'code id_token' : 'code')
parameters.set(
'response_type',
hybrid ? 'code id_token' : implicit ? 'id_token' : 'code',
)
}
if (implicit && !parameters.has('nonce')) {
throw CodedTypeError(
'response_type=id_token clients must provide a nonce parameter in their authorization request parameters',
ERR_INVALID_ARG_VALUE,
)
}
if (jarm) {
parameters.set('response_mode', 'jwt')
@@ -3727,7 +3998,8 @@ export async function fetchUserInfo(
errorHandler(err)
}
getContentType(response) === 'application/jwt' &&
// @ts-expect-error
oauth.getContentType(response) === 'application/jwt' &&
(await nonRepudiation?.(response))
return result
@@ -3788,7 +4060,8 @@ export async function tokenIntrospection(
})
.catch(errorHandler)
getContentType(response) === 'application/token-introspection+jwt' &&
// @ts-expect-error
oauth.getContentType(response) === 'application/token-introspection+jwt' &&
(await nonRepudiation?.(response))
return result
@@ -3997,10 +4270,6 @@ export async function fetchProtectedResource(
return result
}
function getContentType(response: Response): string | undefined {
return response.headers.get('content-type')?.split(';')[0]
}
/**
* @ignore
*
+47 -33
View File
@@ -8,7 +8,7 @@ function label(testCase: Record<string, boolean>) {
const keys = Object.keys(
Object.fromEntries(Object.entries(testCase).filter(([, v]) => v === true)),
)
let msg = `w/ response_type=${testCase.hybrid ? 'code id_token' : 'code'}`
let msg = `w/ response_type=${testCase.hybrid ? 'code id_token' : testCase.implicit ? 'id_token' : 'code'}`
return keys.length ? `${msg}, ${keys.join(', ')}` : msg
}
@@ -28,7 +28,7 @@ export default (QUnit: QUnit) => {
| 'hybrid'
| 'nonrepudiation'
| 'encryption'
| 'login'
| 'implicit'
>
) => {
const conf = {
@@ -38,8 +38,8 @@ export default (QUnit: QUnit) => {
dpop: false,
jwtUserinfo: false,
hybrid: false,
login: false,
encryption: false,
implicit: false,
nonrepudiation: false,
}
for (const flag of flags) {
@@ -64,6 +64,8 @@ export default (QUnit: QUnit) => {
options('jwtUserinfo', 'encryption'),
options('hybrid'),
options('hybrid', 'encryption'),
options('implicit'),
options('implicit', 'encryption'),
]
for (const testCase of testCases) {
@@ -74,6 +76,7 @@ export default (QUnit: QUnit) => {
dpop,
jwtUserinfo,
hybrid,
implicit,
encryption,
nonrepudiation,
} = testCase
@@ -95,7 +98,9 @@ export default (QUnit: QUnit) => {
false,
hybrid
? ['implicit', 'authorization_code', 'refresh_token']
: ['authorization_code', 'refresh_token'],
: implicit
? ['implicit']
: ['authorization_code', 'refresh_token'],
encryption,
)
@@ -112,7 +117,9 @@ export default (QUnit: QUnit) => {
if (hybrid) {
execute.push(client.useCodeIdTokenResponseType)
}
if (implicit) {
execute.push(client.useIdTokenResponseType)
}
const config = await client.discovery(
issuerIdentifier,
metadata.client_id,
@@ -158,7 +165,7 @@ export default (QUnit: QUnit) => {
const maxAge = random() ? 30 : undefined
let nonce: string | undefined
if (hybrid) {
if (hybrid || implicit) {
nonce = client.randomNonce()
params.set('nonce', nonce)
}
@@ -229,36 +236,43 @@ export default (QUnit: QUnit) => {
throw new Error('unreachable')
}
const response = await client.authorizationCodeGrant(
config,
input,
{
expectedNonce: nonce,
pkceCodeVerifier: code_verifier,
},
undefined,
{ DPoP },
)
if (implicit) {
await client.signIn(config, input, nonce!, {
maxAge,
})
} else {
const response = await client.authorizationCodeGrant(
config,
input,
{
expectedNonce: nonce,
pkceCodeVerifier: code_verifier,
maxAge,
},
undefined,
{ DPoP },
)
await client.fetchUserInfo(
config,
response.access_token,
response.claims()?.sub!,
{
DPoP,
},
)
await client.fetchUserInfo(
config,
response.access_token,
response.claims()?.sub!,
{
DPoP,
},
)
await client.refreshTokenGrant(
config,
response.refresh_token!,
undefined,
{
DPoP,
},
)
await client.refreshTokenGrant(
config,
response.refresh_token!,
undefined,
{
DPoP,
},
)
}
if (jarm || hybrid || nonrepudiation) {
if (jarm || hybrid || nonrepudiation || implicit) {
const cache = client.getJwksCache(config)
t.ok(cache?.uat)
t.ok(cache?.jwks)