mirror of
https://github.com/hanzoai/openid-client.git
synced 2026-09-24 17:01:10 +00:00
fix: retry dpop nonce errors for generic grants
This commit is contained in:
+25
-12
@@ -4270,7 +4270,7 @@ export async function genericGrantRequest(
|
||||
checkConfig(config)
|
||||
|
||||
const { as, c, auth, fetch, tlsOnly, timeout, decrypt } = int(config)
|
||||
const result = await oauth
|
||||
const response = await oauth
|
||||
.genericTokenEndpointRequest(
|
||||
as,
|
||||
c,
|
||||
@@ -4285,19 +4285,32 @@ export async function genericGrantRequest(
|
||||
signal: signal(timeout),
|
||||
},
|
||||
)
|
||||
.then((response) => {
|
||||
let recognizedTokenTypes: oauth.RecognizedTokenTypes | undefined
|
||||
if (grantType === 'urn:ietf:params:oauth:grant-type:token-exchange') {
|
||||
recognizedTokenTypes = { n_a: () => {} }
|
||||
}
|
||||
|
||||
return oauth.processGenericTokenEndpointResponse(as, c, response, {
|
||||
[oauth.jweDecrypt]: decrypt,
|
||||
recognizedTokenTypes,
|
||||
})
|
||||
})
|
||||
.catch(errorHandler)
|
||||
|
||||
let recognizedTokenTypes: oauth.RecognizedTokenTypes | undefined
|
||||
if (grantType === 'urn:ietf:params:oauth:grant-type:token-exchange') {
|
||||
recognizedTokenTypes = { n_a: () => {} }
|
||||
}
|
||||
|
||||
const p = oauth.processGenericTokenEndpointResponse(as, c, response, {
|
||||
[oauth.jweDecrypt]: decrypt,
|
||||
recognizedTokenTypes,
|
||||
})
|
||||
|
||||
let result: oauth.TokenEndpointResponse
|
||||
try {
|
||||
result = await p
|
||||
} catch (err) {
|
||||
if (retryable(err, options)) {
|
||||
return genericGrantRequest(config, grantType, parameters, {
|
||||
...options,
|
||||
flag: retry,
|
||||
})
|
||||
}
|
||||
|
||||
errorHandler(err)
|
||||
}
|
||||
|
||||
addHelpers(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import test from 'ava'
|
||||
import * as client from '../src/index.js'
|
||||
import * as undici from 'undici'
|
||||
import * as jose from 'jose'
|
||||
|
||||
test('genericGrantRequest accepts n_a token type in token exchange grant response', async (t) => {
|
||||
let agent = new undici.MockAgent()
|
||||
@@ -123,3 +124,88 @@ test('genericGrantRequest with other grant types does not add n_a token type rec
|
||||
|
||||
t.notThrows(() => agent.assertNoPendingInterceptors())
|
||||
})
|
||||
|
||||
test('genericGrantRequest retries DPoP nonce errors', async (t) => {
|
||||
let agent = new undici.MockAgent()
|
||||
agent.disableNetConnect()
|
||||
|
||||
const mockAgent = agent.get('https://as.example.com')
|
||||
|
||||
mockAgent
|
||||
.intercept({
|
||||
method: 'POST',
|
||||
path: '/token',
|
||||
headers({ dpop }) {
|
||||
return (
|
||||
typeof dpop === 'string' && jose.decodeJwt(dpop).nonce === undefined
|
||||
)
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
400,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
'dpop-nonce': 'use this',
|
||||
'www-authenticate': 'dpop error="use_dpop_nonce"',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
mockAgent
|
||||
.intercept({
|
||||
method: 'POST',
|
||||
path: '/token',
|
||||
headers({ dpop }) {
|
||||
return (
|
||||
typeof dpop === 'string' && jose.decodeJwt(dpop).nonce === 'use this'
|
||||
)
|
||||
},
|
||||
})
|
||||
.reply(
|
||||
200,
|
||||
{
|
||||
access_token: 'test-access-token',
|
||||
token_type: 'DPoP',
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const config = new client.Configuration(
|
||||
{
|
||||
issuer: 'https://as.example.com',
|
||||
token_endpoint: 'https://as.example.com/token',
|
||||
},
|
||||
'test-client-id',
|
||||
undefined,
|
||||
client.None(),
|
||||
)
|
||||
|
||||
// @ts-ignore
|
||||
config[client.customFetch] = (url, options) => {
|
||||
return undici.fetch(url, { ...options, dispatcher: agent })
|
||||
}
|
||||
|
||||
const DPoP = client.getDPoPHandle(
|
||||
config,
|
||||
await client.randomDPoPKeyPair('ES256'),
|
||||
)
|
||||
|
||||
const result = await client.genericGrantRequest(
|
||||
config,
|
||||
'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
{
|
||||
assertion: 'jwt-assertion-value',
|
||||
},
|
||||
{ DPoP },
|
||||
)
|
||||
|
||||
t.is(result.access_token, 'test-access-token')
|
||||
t.is(result.token_type, 'dpop')
|
||||
|
||||
t.notThrows(() => agent.assertNoPendingInterceptors())
|
||||
})
|
||||
Reference in New Issue
Block a user