diff --git a/examples/oauth.ts b/examples/dpop.ts index cc6d632..c3266a7 100644 --- a/examples/oauth.ts +++ b/examples/dpop.ts @@ -14,6 +14,12 @@ let client_secret!: string * Server. */ let redirect_uri!: string +/** + * In order to take full advantage of DPoP you shall generate a random private key for every + * session. In the browser environment you shall use IndexedDB to persist the generated + * CryptoKeyPair. + */ +let DPoP!: CryptoKeyPair // End of prerequisites @@ -71,13 +77,10 @@ let access_token: string throw new Error() // Handle OAuth 2.0 redirect error } - const response = await oauth.authorizationCodeGrantRequest( - as, - client, - params, - redirect_uri, - code_verifier, - ) + const authorizationCodeGrantRequest = () => + oauth.authorizationCodeGrantRequest(as, client, params, redirect_uri, code_verifier, { DPoP }) + + let response = await authorizationCodeGrantRequest() let challenges: oauth.WWWAuthenticateChallenge[] | undefined if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) { @@ -87,10 +90,22 @@ let access_token: string throw new Error() // Handle WWW-Authenticate Challenges as needed } - const result = await oauth.processAuthorizationCodeOAuth2Response(as, client, response) + const processAuthorizationCodeOAuth2Response = () => + oauth.processAuthorizationCodeOAuth2Response(as, client, response) + + let result = await processAuthorizationCodeOAuth2Response() if (oauth.isOAuth2Error(result)) { console.error('Error Response', result) - throw new Error() // Handle OAuth 2.0 response body error + if (result.error === 'use_dpop_nonce') { + // the AS-signalled nonce is now cached, retrying + response = await authorizationCodeGrantRequest() + result = await processAuthorizationCodeOAuth2Response() + if (oauth.isOAuth2Error(result)) { + throw new Error() // Handle OAuth 2.0 response body error + } + } else { + throw new Error() // Handle OAuth 2.0 response body error + } } console.log('Access Token Response', result) @@ -99,18 +114,33 @@ let access_token: string // Protected Resource Request { - const response = await oauth.protectedResourceRequest( - access_token, - 'GET', - new URL('https://rs.example.com/api'), - ) + const protectedResourceRequest = () => + oauth.protectedResourceRequest( + access_token, + 'GET', + new URL('https://rs.example.com/api'), + undefined, + undefined, + { DPoP }, + ) + let response = await protectedResourceRequest() let challenges: oauth.WWWAuthenticateChallenge[] | undefined if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) { - for (const challenge of challenges) { - console.error('WWW-Authenticate Challenge', challenge) + const { 0: challenge, length } = challenges + if ( + length === 1 && + challenge.scheme === 'dpop' && + challenge.parameters.error === 'use_dpop_nonce' + ) { + // the AS-signalled nonce is now cached, retrying + response = await protectedResourceRequest() + } else { + for (const challenge of challenges) { + console.error('WWW-Authenticate Challenge', challenge) + } + throw new Error() // Handle WWW-Authenticate Challenges as needed } - throw new Error() // Handle WWW-Authenticate Challenges as needed } console.log('Protected Resource Response', await response.json())