Skip to content

Commit

Permalink
docs: update examples
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Jan 11, 2024
1 parent 78488fd commit 779cf60
Show file tree
Hide file tree
Showing 18 changed files with 404 additions and 163 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.5.0/mod.ts'
- Pushed Authorization Request (PAR) - [source](examples/par.ts) | [diff from code flow](examples/par.diff)
- Client Credentials Grant - [source](examples/client_credentials.ts)
- Device Authorization Grant - [source](examples/device_authorization_grant.ts)
- FAPI 2.0 (Private Key JWT, PAR, DPoP) - [source](examples/fapi2.ts)
- FAPI 1.0 Advanced (Private Key JWT, MTLS, JAR) - [source](examples/fapi1-advanced.ts)
- FAPI 2.0 Security Profile (Private Key JWT, PAR, DPoP) - [source](examples/fapi2.ts)
- FAPI 2.0 Message Signing (Private Key JWT, PAR, DPoP, JAR, JARM) - [source](examples/fapi2-message-signing.ts) | [diff](examples/fapi2-message-signing.diff)

## Supported Runtimes
Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ A collection of examples for the most common use cases.
- Pushed Authorization Request (PAR) - [source](par.ts) | [diff from code flow](par.diff)
- Client Credentials Grant - [source](client_credentials.ts)
- Device Authorization Grant - [source](device_authorization_grant.ts)
- FAPI 2.0 (Private Key JWT, PAR, DPoP) - [source](fapi2.ts)
- FAPI 1.0 Advanced (Private Key JWT, MTLS, JAR) - [source](fapi1-advanced.ts)
- FAPI 2.0 Security Profile (Private Key JWT, PAR, DPoP) - [source](fapi2.ts)
- FAPI 2.0 Message Signing (Private Key JWT, PAR, DPoP, JAR, JARM) - [source](fapi2-message-signing.ts) | [diff](fapi2-message-signing.diff)
16 changes: 11 additions & 5 deletions examples/client_credentials.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as oauth from '../src/index.js'
import * as oauth from '../src/index.js' // replace with an import of oauth4webapi

// Prerequisites

let issuer!: URL // Authorization server's Issuer Identifier URL
let client_id!: string
let client_secret!: string

// End of prerequisites

const issuer = new URL('https://example.as.com')
const as = await oauth
.discoveryRequest(issuer)
.then((response) => oauth.processDiscoveryResponse(issuer, response))

const client: oauth.Client = {
client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
client_secret:
'ddce41c3d7618bb30e8a5e5e423fce223427426265ebc96fd9dd5713a6d4fc58bc523c45af42274c210ab18d4a93b5b7169edf6236ed2657f6be64ec41b72f87',
client_id,
client_secret,
token_endpoint_auth_method: 'client_secret_basic',
}

Expand Down
30 changes: 20 additions & 10 deletions examples/code.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import * as oauth from '../src/index.js'
import * as oauth from '../src/index.js' // replace with an import of oauth4webapi

// Prerequisites

let issuer!: URL // Authorization server's Issuer Identifier URL
let client_id!: string
let client_secret!: string
/**
* Value used in the authorization request as redirect_uri pre-registered at the Authorization
* Server.
*/
let redirect_uri!: string

// End of prerequisites

const issuer = new URL('https://example.as.com')
const as = await oauth
.discoveryRequest(issuer)
.then((response) => oauth.processDiscoveryResponse(issuer, response))

const client: oauth.Client = {
client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
client_secret:
'ddce41c3d7618bb30e8a5e5e423fce223427426265ebc96fd9dd5713a6d4fc58bc523c45af42274c210ab18d4a93b5b7169edf6236ed2657f6be64ec41b72f87',
client_id,
client_secret,
token_endpoint_auth_method: 'client_secret_basic',
}

const redirect_uri = 'https://example.rp.com/cb'

if (as.code_challenge_methods_supported?.includes('S256') !== true) {
// This example assumes S256 PKCE support is signalled
// If it isn't supported, random `nonce` must be used for CSRF protection.
/**
* 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()
}

Expand All @@ -26,7 +37,6 @@ const code_challenge_method = 'S256'

{
// 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)
Expand Down
12 changes: 9 additions & 3 deletions examples/device_authorization_grant.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import * as oauth from '../src/index.js'
import * as oauth from '../src/index.js' // replace with an import of oauth4webapi

// Prerequisites

let issuer!: URL // Authorization server's Issuer Identifier URL
let client_id!: string

// End of prerequisites

const issuer = new URL('https://example.as.com')
const as = await oauth
.discoveryRequest(issuer)
.then((response) => oauth.processDiscoveryResponse(issuer, response))

const client: oauth.Client = {
client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
client_id,
token_endpoint_auth_method: 'none',
}

Expand Down
29 changes: 16 additions & 13 deletions examples/dpop.diff
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
diff --git a/examples/code.ts b/examples/dpop.ts
index 98576c1..aa2cb90 100644
index 895d96e..5ff36e6 100644
--- a/examples/code.ts
+++ b/examples/dpop.ts
@@ -1,5 +1,9 @@
import * as oauth from '../src/index.js'
@@ -10,6 +10,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

+// 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
+const DPoP = await oauth.generateKeyPair('ES256')
+
const issuer = new URL('https://example.as.com')
const as = await oauth
.discoveryRequest(issuer)
@@ -54,6 +58,7 @@ let access_token: string
// End of prerequisites

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

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

@@ -79,12 +87,15 @@ let access_token: string
@@ -89,12 +99,15 @@ let access_token: string

// fetch userinfo response
{
Expand Down
40 changes: 26 additions & 14 deletions examples/dpop.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
import * as oauth from '../src/index.js'
import * as oauth from '../src/index.js' // replace with an import of oauth4webapi

// Prerequisites

let issuer!: URL // Authorization server's Issuer Identifier URL
let client_id!: string
let client_secret!: string
/**
* Value used in the authorization request as redirect_uri pre-registered at the Authorization
* 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

// 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
const DPoP = await oauth.generateKeyPair('ES256')

const issuer = new URL('https://example.as.com')
const as = await oauth
.discoveryRequest(issuer)
.then((response) => oauth.processDiscoveryResponse(issuer, response))

const client: oauth.Client = {
client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
client_secret:
'ddce41c3d7618bb30e8a5e5e423fce223427426265ebc96fd9dd5713a6d4fc58bc523c45af42274c210ab18d4a93b5b7169edf6236ed2657f6be64ec41b72f87',
client_id,
client_secret,
token_endpoint_auth_method: 'client_secret_basic',
}

const redirect_uri = 'https://example.rp.com/cb'

if (as.code_challenge_methods_supported?.includes('S256') !== true) {
// This example assumes S256 PKCE support is signalled
// If it isn't supported, random `nonce` must be used for CSRF protection.
/**
* 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()
}

Expand All @@ -30,7 +43,6 @@ const code_challenge_method = 'S256'

{
// 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)
Expand Down
125 changes: 125 additions & 0 deletions examples/fapi1-advanced.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import * as undici from 'undici'
import * as oauth from '../src/index.js' // replace with an import of oauth4webapi

// Prerequisites

let issuer!: URL // Authorization server's Issuer Identifier URL
let client_id!: string
/**
* Value used in the authorization request as redirect_uri pre-registered at the Authorization
* Server.
*/
let redirect_uri!: string
/** A key corresponding to the mtlsClientCertificate. */
let mtlsClientKey!: string
/**
* A certificate the client has pre-registered at the Authorization Server for use with Mutual-TLS
* client authentication method.
*/
let mtlsClientCertificate!: string
/**
* A key that is pre-registered at the Authorization Server that the client is supposed to sign its
* Request Objects with.
*/
let jarPrivateKey!: CryptoKey
/**
* A key that the client has pre-registered at the Authorization Server for use with Private Key JWT
* client authentication method.
*/
let clientPrivateKey!: CryptoKey

// End of prerequisites

const as = await oauth
.discoveryRequest(issuer)
.then((response) => oauth.processDiscoveryResponse(issuer, response))

const client: oauth.Client = {
client_id,
token_endpoint_auth_method: 'private_key_jwt',
}

const nonce = oauth.generateRandomNonce()
const code_verifier = oauth.generateRandomCodeVerifier()
const code_challenge = await oauth.calculatePKCECodeChallenge(code_verifier)
const code_challenge_method = 'S256'

let request: 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 id_token')
params.set('scope', 'openid email')
params.set('nonce', nonce)

request = await oauth.issueRequestObject(as, client, params, jarPrivateKey)
}

{
// 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', request)
}

// one eternity later, the user lands back on the redirect_uri
{
// @ts-expect-error
const authorizationResponse: URLSearchParams = getAuthorizationResponse()
const params = await oauth.experimental_validateDetachedSignatureResponse(
as,
client,
authorizationResponse,
nonce,
)
if (oauth.isOAuth2Error(params)) {
console.log('error', params)
throw new Error() // Handle OAuth 2.0 redirect error
}

const response = await oauth.authorizationCodeGrantRequest(
as,
client,
params,
redirect_uri,
code_verifier,
{
clientPrivateKey,
[oauth.experimental_useMtlsAlias]: true,
// @ts-expect-error
[oauth.experimental_customFetch]: (...args) => {
// @ts-expect-error
return undici.fetch(args[0], {
...args[1],
dispatcher: new undici.Agent({
connect: {
key: mtlsClientKey,
cert: mtlsClientCertificate,
},
}),
})
},
},
)

let challenges: oauth.WWWAuthenticateChallenge[] | undefined
if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) {
for (const challenge of challenges) {
console.log('challenge', challenge)
}
throw new Error() // Handle www-authenticate challenges as needed
}

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

console.log('result', result)
const claims = oauth.getValidatedIdTokenClaims(result)
console.log('ID Token Claims', claims)
}

0 comments on commit 779cf60

Please sign in to comment.