Skip to content

Commit

Permalink
refactor!: make DPoP implementation tree-shakeable
Browse files Browse the repository at this point in the history
BREAKING CHANGE: DPoP request options are now obtained by calling the
`DPoP()` exported function. This returns a handle that also maintains
its own LRU nonce caches
  • Loading branch information
panva committed Oct 7, 2024
1 parent cefcf32 commit 1fca2a3
Show file tree
Hide file tree
Showing 18 changed files with 252 additions and 201 deletions.
24 changes: 17 additions & 7 deletions conformance/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,14 @@ export const flow = (options?: MacroOptions) => {
authorizationUrl.searchParams.set('response_type', response_type)
}

let DPoP!: oauth.CryptoKeyPair
let DPoPKeyPair!: oauth.CryptoKeyPair
let DPoP!: oauth.DPoPRequestOptions['DPoP']
if (usesDpop(variant)) {
DPoP = await oauth.generateKeyPair(JWS_ALGORITHM as oauth.JWSAlgorithm)
DPoPKeyPair = await oauth.generateKeyPair(JWS_ALGORITHM as oauth.JWSAlgorithm)
DPoP = oauth.DPoP(client, DPoPKeyPair)
authorizationUrl.searchParams.set(
'dpop_jkt',
await calculateJwkThumbprint(await exportJWK(DPoP.publicKey)),
await calculateJwkThumbprint(await exportJWK(DPoPKeyPair.publicKey)),
)
}

Expand All @@ -325,7 +327,11 @@ export const flow = (options?: MacroOptions) => {
try {
result = await oauth.processPushedAuthorizationResponse(as, client, par)
} catch (err) {
if (DPoP && err instanceof oauth.ResponseBodyError && err.error === 'use_dpop_nonce') {
if (
DPoPKeyPair &&
err instanceof oauth.ResponseBodyError &&
err.error === 'use_dpop_nonce'
) {
t.log('error', inspect(err, { depth: Infinity }))
t.log('retrying with a newly obtained dpop nonce')
par = await request()
Expand Down Expand Up @@ -399,7 +405,11 @@ export const flow = (options?: MacroOptions) => {
result = await oauth.processAuthorizationCodeResponse(as, client, response)
}
} catch (err) {
if (DPoP && err instanceof oauth.ResponseBodyError && err.error === 'use_dpop_nonce') {
if (
DPoPKeyPair &&
err instanceof oauth.ResponseBodyError &&
err.error === 'use_dpop_nonce'
) {
t.log('error', inspect(err, { depth: Infinity }))
t.log('retrying with a newly obtained dpop nonce')
response = await request()
Expand Down Expand Up @@ -443,7 +453,7 @@ export const flow = (options?: MacroOptions) => {
t.log('userinfo endpoint response', { ...result })
} catch (err) {
t.log('error', inspect(err, { depth: Infinity }))
if (DPoP && err instanceof oauth.WWWAuthenticateChallengeError) {
if (DPoPKeyPair && err instanceof oauth.WWWAuthenticateChallengeError) {
const { 0: challenge, length } = err.cause
if (
length === 1 &&
Expand Down Expand Up @@ -483,7 +493,7 @@ export const flow = (options?: MacroOptions) => {
accounts = await request()
} catch (err) {
t.log('error', inspect(err, { depth: Infinity }))
if (DPoP && err instanceof oauth.WWWAuthenticateChallengeError) {
if (DPoPKeyPair && err instanceof oauth.WWWAuthenticateChallengeError) {
const { 0: challenge, length } = err.cause
if (
length === 1 &&
Expand Down
16 changes: 12 additions & 4 deletions examples/dpop.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/examples/oauth.ts b/examples/dpop.ts
index d55e62d..0cb9bdd 100644
index d55e62d..29b3c7f 100644
--- a/examples/oauth.ts
+++ b/examples/dpop.ts
@@ -15,6 +15,12 @@ let client_secret!: string
Expand All @@ -11,11 +11,19 @@ index d55e62d..0cb9bdd 100644
+ * session. In the browser environment you shall use IndexedDB to persist the generated
+ * CryptoKeyPair.
+ */
+let DPoP!: oauth.CryptoKeyPair
+let DPoPKeys!: oauth.CryptoKeyPair

// End of prerequisites

@@ -64,16 +70,32 @@ let access_token: string
@@ -24,6 +30,7 @@ const as = await oauth

const client: oauth.Client = { client_id }
const clientAuth = oauth.ClientSecretPost(client_secret)
+const DPoP = oauth.DPoP(client, DPoPKeys)

const code_challenge_method = 'S256'
/**
@@ -64,16 +71,32 @@ let access_token: string
const currentUrl: URL = getCurrentUrl()
const params = oauth.validateAuthResponse(as, client, currentUrl, state)

Expand Down Expand Up @@ -57,7 +65,7 @@ index d55e62d..0cb9bdd 100644

console.log('Access Token Response', result)
;({ access_token } = result)
@@ -81,11 +103,29 @@ let access_token: string
@@ -81,11 +104,29 @@ let access_token: string

// Protected Resource Request
{
Expand Down
3 changes: 2 additions & 1 deletion examples/dpop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let redirect_uri!: string
* session. In the browser environment you shall use IndexedDB to persist the generated
* CryptoKeyPair.
*/
let DPoP!: oauth.CryptoKeyPair
let DPoPKeys!: oauth.CryptoKeyPair

// End of prerequisites

Expand All @@ -30,6 +30,7 @@ const as = await oauth

const client: oauth.Client = { client_id }
const clientAuth = oauth.ClientSecretPost(client_secret)
const DPoP = oauth.DPoP(client, DPoPKeys)

const code_challenge_method = 'S256'
/**
Expand Down
14 changes: 7 additions & 7 deletions examples/fapi2-message-signing.diff
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
diff --git a/examples/fapi2.ts b/examples/fapi2-message-signing.ts
index b8ec053..3fe9af1 100644
index e49247d..76ce800 100644
--- a/examples/fapi2.ts
+++ b/examples/fapi2-message-signing.ts
@@ -25,6 +25,11 @@ let DPoP!: oauth.CryptoKeyPair
@@ -25,6 +25,11 @@ let DPoPKeys!: oauth.CryptoKeyPair
* client authentication method.
*/
let clientPrivateKey!: oauth.CryptoKey
Expand All @@ -14,7 +14,7 @@ index b8ec053..3fe9af1 100644

// End of prerequisites

@@ -44,8 +49,8 @@ const code_challenge_method = 'S256'
@@ -45,8 +50,8 @@ const code_challenge_method = 'S256'
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)

Expand All @@ -25,7 +25,7 @@ index b8ec053..3fe9af1 100644
{
const params = new URLSearchParams()
params.set('client_id', client.client_id)
@@ -53,7 +58,18 @@ let request_uri: string
@@ -54,7 +59,18 @@ let request_uri: string
params.set('code_challenge_method', code_challenge_method)
params.set('redirect_uri', redirect_uri)
params.set('response_type', 'code')
Expand All @@ -45,7 +45,7 @@ index b8ec053..3fe9af1 100644

const pushedAuthorizationRequest = () =>
oauth.pushedAuthorizationRequest(as, client, clientAuth, params, {
@@ -91,7 +107,7 @@ let request_uri: string
@@ -92,7 +108,7 @@ let request_uri: string
let access_token: string
{
const currentUrl: URL = getCurrentUrl()
Expand All @@ -54,7 +54,7 @@ index b8ec053..3fe9af1 100644

const authorizationCodeGrantRequest = () =>
oauth.authorizationCodeGrantRequest(
@@ -107,7 +123,7 @@ let access_token: string
@@ -108,7 +124,7 @@ let access_token: string
let response = await authorizationCodeGrantRequest()

const processAuthorizationCodeResponse = () =>
Expand All @@ -63,7 +63,7 @@ index b8ec053..3fe9af1 100644

let result = await processAuthorizationCodeResponse().catch(async (err) => {
if (err instanceof oauth.ResponseBodyError) {
@@ -120,6 +136,9 @@ let access_token: string
@@ -121,6 +137,9 @@ let access_token: string
throw err
})

Expand Down
3 changes: 2 additions & 1 deletion examples/fapi2-message-signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let redirect_uri!: string
* session. In the browser environment you shall use IndexedDB to persist the generated
* CryptoKeyPair.
*/
let DPoP!: oauth.CryptoKeyPair
let DPoPKeys!: oauth.CryptoKeyPair
/**
* A key that the client has pre-registered at the Authorization Server for use with Private Key JWT
* client authentication method.
Expand All @@ -39,6 +39,7 @@ const as = await oauth

const client: oauth.Client = { client_id }
const clientAuth = oauth.PrivateKeyJwt(clientPrivateKey)
const DPoP = oauth.DPoP(client, DPoPKeys)

const code_challenge_method = 'S256'
/**
Expand Down
11 changes: 6 additions & 5 deletions examples/fapi2.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/examples/oauth.ts b/examples/fapi2.ts
index d55e62d..b8ec053 100644
index d55e62d..e49247d 100644
--- a/examples/oauth.ts
+++ b/examples/fapi2.ts
@@ -9,12 +9,22 @@ let algorithm!:
Expand All @@ -17,7 +17,7 @@ index d55e62d..b8ec053 100644
+ * session. In the browser environment you shall use IndexedDB to persist the generated
+ * CryptoKeyPair.
+ */
+let DPoP!: oauth.CryptoKeyPair
+let DPoPKeys!: oauth.CryptoKeyPair
+/**
+ * A key that the client has pre-registered at the Authorization Server for use with Private Key JWT
+ * client authentication method.
Expand All @@ -26,12 +26,13 @@ index d55e62d..b8ec053 100644

// End of prerequisites

@@ -23,36 +33,55 @@ const as = await oauth
@@ -23,36 +33,56 @@ const as = await oauth
.then((response) => oauth.processDiscoveryResponse(issuer, response))

const client: oauth.Client = { client_id }
-const clientAuth = oauth.ClientSecretPost(client_secret)
+const clientAuth = oauth.PrivateKeyJwt(clientPrivateKey)
+const DPoP = oauth.DPoP(client, DPoPKeys)

const code_challenge_method = 'S256'
/**
Expand Down Expand Up @@ -100,7 +101,7 @@ index d55e62d..b8ec053 100644

// now redirect the user to authorizationUrl.href
}
@@ -62,18 +91,34 @@ let state: string | undefined
@@ -62,18 +92,34 @@ let state: string | undefined
let access_token: string
{
const currentUrl: URL = getCurrentUrl()
Expand Down Expand Up @@ -145,7 +146,7 @@ index d55e62d..b8ec053 100644

console.log('Access Token Response', result)
;({ access_token } = result)
@@ -81,11 +126,29 @@ let access_token: string
@@ -81,11 +127,29 @@ let access_token: string

// Protected Resource Request
{
Expand Down
3 changes: 2 additions & 1 deletion examples/fapi2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let redirect_uri!: string
* session. In the browser environment you shall use IndexedDB to persist the generated
* CryptoKeyPair.
*/
let DPoP!: oauth.CryptoKeyPair
let DPoPKeys!: oauth.CryptoKeyPair
/**
* A key that the client has pre-registered at the Authorization Server for use with Private Key JWT
* client authentication method.
Expand All @@ -34,6 +34,7 @@ const as = await oauth

const client: oauth.Client = { client_id }
const clientAuth = oauth.PrivateKeyJwt(clientPrivateKey)
const DPoP = oauth.DPoP(client, DPoPKeys)

const code_challenge_method = 'S256'
/**
Expand Down
Loading

0 comments on commit 1fca2a3

Please sign in to comment.