Skip to content

Commit

Permalink
example: update example nonce/state handling
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Feb 6, 2024
1 parent e4c3c56 commit 1140165
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 108 deletions.
33 changes: 20 additions & 13 deletions examples/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,34 @@ const client: oauth.Client = {
token_endpoint_auth_method: 'client_secret_basic',
}

if (as.code_challenge_methods_supported?.includes('S256') !== true) {
/**
* This example assumes S256 PKCE support is signalled. If it isn't supported, a unique random
* `nonce` for each authorization request must be used for CSRF protection.
*/
throw new Error()
}

const code_challenge_method = 'S256'
/**
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
* the code_verifier and nonce in the end-user session such that it can be recovered as the user
* gets redirected from the authorization server back to your application.
*/
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
const code_challenge_method = 'S256'
let nonce: string | undefined

{
// redirect user to as.authorization_endpoint
const authorizationUrl = new URL(as.authorization_endpoint!)
authorizationUrl.searchParams.set('client_id', client.client_id)
authorizationUrl.searchParams.set('code_challenge', code_challenge)
authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)
authorizationUrl.searchParams.set('redirect_uri', redirect_uri)
authorizationUrl.searchParams.set('response_type', 'code')
authorizationUrl.searchParams.set('scope', 'openid email')
authorizationUrl.searchParams.set('code_challenge', code_challenge)
authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)

/**
* We cannot be sure the AS supports PKCE so we're going to use nonce too. Use of PKCE is
* backwards compatible even if the AS doesn't support it which is why we're using it regardless.
*/
if (as.code_challenge_methods_supported?.includes('S256') !== true) {
nonce = oauth.generateRandomNonce()
authorizationUrl.searchParams.set('nonce', nonce)
}
}

// one eternity later, the user lands back on the redirect_uri
Expand All @@ -52,7 +59,7 @@ let access_token: string
{
// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
const params = oauth.validateAuthResponse(as, client, currentUrl, oauth.expectNoState)
const params = oauth.validateAuthResponse(as, client, currentUrl)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
Expand All @@ -74,7 +81,7 @@ let access_token: string
throw new Error() // Handle www-authenticate challenges as needed
}

const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response)
const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response, nonce)
if (oauth.isOAuth2Error(result)) {
console.log('error', result)
throw new Error() // Handle OAuth 2.0 response body error
Expand Down
10 changes: 5 additions & 5 deletions examples/dpop.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/examples/code.ts b/examples/dpop.ts
index 895d96e..5ff36e6 100644
index 1405ead..5b6188c 100644
--- a/examples/code.ts
+++ b/examples/dpop.ts
@@ -10,6 +10,12 @@ let client_secret!: string
Expand All @@ -15,16 +15,16 @@ index 895d96e..5ff36e6 100644

// End of prerequisites

@@ -64,6 +70,7 @@ let access_token: string
@@ -71,6 +77,7 @@ let access_token: string
params,
redirect_uri,
code_verifier,
+ { DPoP },
)

let challenges: oauth.WWWAuthenticateChallenge[] | undefined
@@ -77,6 +84,9 @@ let access_token: string
const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response)
@@ -84,6 +91,9 @@ let access_token: string
const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response, nonce)
if (oauth.isOAuth2Error(result)) {
console.log('error', result)
+ if (result.error === 'use_dpop_nonce') {
Expand All @@ -33,7 +33,7 @@ index 895d96e..5ff36e6 100644
throw new Error() // Handle OAuth 2.0 response body error
}

@@ -89,12 +99,15 @@ let access_token: string
@@ -96,12 +106,15 @@ let access_token: string

// fetch userinfo response
{
Expand Down
33 changes: 20 additions & 13 deletions examples/dpop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,34 @@ const client: oauth.Client = {
token_endpoint_auth_method: 'client_secret_basic',
}

if (as.code_challenge_methods_supported?.includes('S256') !== true) {
/**
* This example assumes S256 PKCE support is signalled. If it isn't supported, a unique random
* `nonce` for each authorization request must be used for CSRF protection.
*/
throw new Error()
}

const code_challenge_method = 'S256'
/**
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
* the code_verifier and nonce in the end-user session such that it can be recovered as the user
* gets redirected from the authorization server back to your application.
*/
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
const code_challenge_method = 'S256'
let nonce: string | undefined

{
// redirect user to as.authorization_endpoint
const authorizationUrl = new URL(as.authorization_endpoint!)
authorizationUrl.searchParams.set('client_id', client.client_id)
authorizationUrl.searchParams.set('code_challenge', code_challenge)
authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)
authorizationUrl.searchParams.set('redirect_uri', redirect_uri)
authorizationUrl.searchParams.set('response_type', 'code')
authorizationUrl.searchParams.set('scope', 'openid email')
authorizationUrl.searchParams.set('code_challenge', code_challenge)
authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)

/**
* We cannot be sure the AS supports PKCE so we're going to use nonce too. Use of PKCE is
* backwards compatible even if the AS doesn't support it which is why we're using it regardless.
*/
if (as.code_challenge_methods_supported?.includes('S256') !== true) {
nonce = oauth.generateRandomNonce()
authorizationUrl.searchParams.set('nonce', nonce)
}
}

// one eternity later, the user lands back on the redirect_uri
Expand All @@ -58,7 +65,7 @@ let access_token: string
{
// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
const params = oauth.validateAuthResponse(as, client, currentUrl, oauth.expectNoState)
const params = oauth.validateAuthResponse(as, client, currentUrl)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
Expand All @@ -81,7 +88,7 @@ let access_token: string
throw new Error() // Handle www-authenticate challenges as needed
}

const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response)
const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response, nonce)
if (oauth.isOAuth2Error(result)) {
console.log('error', result)
if (result.error === 'use_dpop_nonce') {
Expand Down
6 changes: 3 additions & 3 deletions examples/fapi2-message-signing.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/examples/fapi2.ts b/examples/fapi2-message-signing.ts
index 2f73b7a..f49f057 100644
index 6899fee..d558e4e 100644
--- a/examples/fapi2.ts
+++ b/examples/fapi2-message-signing.ts
@@ -20,6 +20,11 @@ let DPoP!: CryptoKeyPair
Expand Down Expand Up @@ -46,8 +46,8 @@ index 2f73b7a..f49f057 100644
{
// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
- const params = oauth.validateAuthResponse(as, client, currentUrl, oauth.expectNoState)
+ const params = await oauth.validateJwtAuthResponse(as, client, currentUrl, oauth.expectNoState)
- const params = oauth.validateAuthResponse(as, client, currentUrl)
+ const params = await oauth.validateJwtAuthResponse(as, client, currentUrl)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
2 changes: 1 addition & 1 deletion examples/fapi2-message-signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ let request_uri: string
{
// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
const params = await oauth.validateJwtAuthResponse(as, client, currentUrl, oauth.expectNoState)
const params = await oauth.validateJwtAuthResponse(as, client, currentUrl)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
Expand Down
2 changes: 1 addition & 1 deletion examples/fapi2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ let request_uri: string
{
// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
const params = oauth.validateAuthResponse(as, client, currentUrl, oauth.expectNoState)
const params = oauth.validateAuthResponse(as, client, currentUrl)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
Expand Down
31 changes: 19 additions & 12 deletions examples/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,41 @@ const client: oauth.Client = {
token_endpoint_auth_method: 'client_secret_basic',
}

if (as.code_challenge_methods_supported?.includes('S256') !== true) {
/**
* This example assumes S256 PKCE support is signalled. If it isn't supported, a unique random
* `state` for each authorization request must be used for CSRF protection.
*/
throw new Error()
}

const code_challenge_method = 'S256'
/**
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
* the code_verifier and nonce in the end-user session such that it can be recovered as the user
* gets redirected from the authorization server back to your application.
*/
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
const code_challenge_method = 'S256'
let state: string | undefined

{
// redirect user to as.authorization_endpoint
const authorizationUrl = new URL(as.authorization_endpoint!)
authorizationUrl.searchParams.set('client_id', client.client_id)
authorizationUrl.searchParams.set('code_challenge', code_challenge)
authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)
authorizationUrl.searchParams.set('redirect_uri', redirect_uri)
authorizationUrl.searchParams.set('response_type', 'code')
authorizationUrl.searchParams.set('scope', 'api:read')
authorizationUrl.searchParams.set('code_challenge', code_challenge)
authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)

/**
* We cannot be sure the AS supports PKCE so we're going to use state too. Use of PKCE is
* backwards compatible even if the AS doesn't support it which is why we're using it regardless.
*/
if (as.code_challenge_methods_supported?.includes('S256') !== true) {
state = oauth.generateRandomState()
authorizationUrl.searchParams.set('state', state)
}
}

// one eternity later, the user lands back on the redirect_uri

// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
const parameters = oauth.validateAuthResponse(as, client, currentUrl, oauth.expectNoState)
const parameters = oauth.validateAuthResponse(as, client, currentUrl, state)
if (oauth.isOAuth2Error(parameters)) {
console.log('error', parameters)
throw new Error() // Handle OAuth 2.0 redirect error
Expand Down
45 changes: 29 additions & 16 deletions examples/par.diff
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
diff --git a/examples/code.ts b/examples/par.ts
index 895d96e..d77092b 100644
index 1405ead..7da916d 100644
--- a/examples/code.ts
+++ b/examples/par.ts
@@ -35,15 +35,40 @@ const code_verifier = oauth.generateRandomCodeVerifier()
@@ -33,15 +33,15 @@ const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
const code_challenge_method = 'S256'
let nonce: string | undefined

+let request_uri: string
+{
{
- // redirect user to as.authorization_endpoint
- const authorizationUrl = new URL(as.authorization_endpoint!)
- authorizationUrl.searchParams.set('client_id', client.client_id)
- authorizationUrl.searchParams.set('redirect_uri', redirect_uri)
- authorizationUrl.searchParams.set('response_type', 'code')
- authorizationUrl.searchParams.set('scope', 'openid email')
- authorizationUrl.searchParams.set('code_challenge', code_challenge)
- authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)
+ const params = new URLSearchParams()
+ params.set('client_id', client.client_id)
+ params.set('code_challenge', code_challenge)
+ params.set('code_challenge_method', code_challenge_method)
+ params.set('redirect_uri', redirect_uri)
+ params.set('response_type', 'code')
+ params.set('scope', 'openid email')
+ params.set('code_challenge', code_challenge)
+ params.set('code_challenge_method', code_challenge_method)

/**
* We cannot be sure the AS supports PKCE so we're going to use nonce too. Use of PKCE is
@@ -49,8 +49,33 @@ let nonce: string | undefined
*/
if (as.code_challenge_methods_supported?.includes('S256') !== true) {
nonce = oauth.generateRandomNonce()
- authorizationUrl.searchParams.set('nonce', nonce)
+ params.set('nonce', nonce)
+ }
+
+ const response = await oauth.pushedAuthorizationRequest(as, client, params)
+ let challenges: oauth.WWWAuthenticateChallenge[] | undefined
Expand All @@ -29,21 +47,16 @@ index 895d96e..d77092b 100644
+ if (oauth.isOAuth2Error(result)) {
+ console.log('error', result)
+ throw new Error() // Handle OAuth 2.0 response body error
+ }
}
+
+ console.log('result', result)
+ ;({ request_uri } = result)
+}
+
{
// redirect user to as.authorization_endpoint
const authorizationUrl = new URL(as.authorization_endpoint!)
authorizationUrl.searchParams.set('client_id', client.client_id)
- authorizationUrl.searchParams.set('code_challenge', code_challenge)
- authorizationUrl.searchParams.set('code_challenge_method', code_challenge_method)
- authorizationUrl.searchParams.set('redirect_uri', redirect_uri)
- authorizationUrl.searchParams.set('response_type', 'code')
- authorizationUrl.searchParams.set('scope', 'openid email')
+{
+ // redirect user to as.authorization_endpoint
+ const authorizationUrl = new URL(as.authorization_endpoint!)
+ authorizationUrl.searchParams.set('client_id', client.client_id)
+ authorizationUrl.searchParams.set('request_uri', request_uri)
}

Expand Down
33 changes: 20 additions & 13 deletions examples/par.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,34 @@ const client: oauth.Client = {
token_endpoint_auth_method: 'client_secret_basic',
}

if (as.code_challenge_methods_supported?.includes('S256') !== true) {
/**
* This example assumes S256 PKCE support is signalled. If it isn't supported, a unique random
* `nonce` for each authorization request must be used for CSRF protection.
*/
throw new Error()
}

const code_challenge_method = 'S256'
/**
* The following MUST be generated for every redirect to the authorization_endpoint. You must store
* the code_verifier and nonce in the end-user session such that it can be recovered as the user
* gets redirected from the authorization server back to your application.
*/
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
const code_challenge_method = 'S256'
let nonce: string | undefined

let request_uri: string
{
const params = new URLSearchParams()
params.set('client_id', client.client_id)
params.set('code_challenge', code_challenge)
params.set('code_challenge_method', code_challenge_method)
params.set('redirect_uri', redirect_uri)
params.set('response_type', 'code')
params.set('scope', 'openid email')
params.set('code_challenge', code_challenge)
params.set('code_challenge_method', code_challenge_method)

/**
* We cannot be sure the AS supports PKCE so we're going to use nonce too. Use of PKCE is
* backwards compatible even if the AS doesn't support it which is why we're using it regardless.
*/
if (as.code_challenge_methods_supported?.includes('S256') !== true) {
nonce = oauth.generateRandomNonce()
params.set('nonce', nonce)
}

const response = await oauth.pushedAuthorizationRequest(as, client, params)
let challenges: oauth.WWWAuthenticateChallenge[] | undefined
Expand Down Expand Up @@ -77,7 +84,7 @@ let access_token: string
{
// @ts-expect-error
const currentUrl: URL = getCurrentUrl()
const params = oauth.validateAuthResponse(as, client, currentUrl, oauth.expectNoState)
const params = oauth.validateAuthResponse(as, client, currentUrl)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
Expand All @@ -99,7 +106,7 @@ let access_token: string
throw new Error() // Handle www-authenticate challenges as needed
}

const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response)
const result = await oauth.processAuthorizationCodeOpenIDResponse(as, client, response, nonce)
if (oauth.isOAuth2Error(result)) {
console.log('error', result)
throw new Error() // Handle OAuth 2.0 response body error
Expand Down
Loading

0 comments on commit 1140165

Please sign in to comment.