Skip to content

Commit

Permalink
refactor!: remove modifyAssertion from the PrivateKey interface
Browse files Browse the repository at this point in the history
The modifyAssertion symbol property of the PrivateKey interface has been
removed. Instead, all functions that issue assertions now accept an
options argument where this symbol can be present.
  • Loading branch information
panva committed Oct 7, 2024
1 parent 8cd2058 commit 4d8b9e8
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 59 deletions.
85 changes: 31 additions & 54 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,6 @@ export interface PrivateKey {
* ID) will be added to the JOSE Header.
*/
kid?: string

/**
* Use to modify the JWT signed by this key right before it is signed.
*
* @see {@link modifyAssertion}
*/
[modifyAssertion]?: ModifyAssertionFunction
}

const ERR_INVALID_ARG_VALUE = 'ERR_INVALID_ARG_VALUE'
Expand Down Expand Up @@ -289,14 +282,12 @@ export const customFetch: unique symbol = Symbol()
* Changing Private Key JWT client assertion audience issued from an array to a string
*
* ```ts
* // Prerequisites
* let as!: oauth.AuthorizationServer
* let client!: oauth.Client
* let parameters!: URLSearchParams
* let clientPrivateKey!: oauth.CryptoKey
* let clientPrivateKey!: oauth.CryptoKey | oauth.PrivateKey
*
* let clientAuth = oauth.PrivateKeyJwt({
* key: clientPrivateKey,
* let clientAuth = oauth.PrivateKeyJwt(clientPrivateKey, {
* [oauth.modifyAssertion](header, payload) {
* payload.aud = as.issuer
* },
Expand All @@ -310,14 +301,12 @@ export const customFetch: unique symbol = Symbol()
* Changing Request Object issued by {@link issueRequestObject} to have an expiration of 5 minutes
*
* ```ts
* // Prerequisites
* let as!: oauth.AuthorizationServer
* let client!: oauth.Client
* let parameters!: URLSearchParams
* let jarPrivateKey!: oauth.CryptoKey
* let jarPrivateKey!: oauth.CryptoKey | oauth.PrivateKey
*
* const request = await oauth.issueRequestObject(as, client, parameters, {
* key: jarPrivateKey,
* const request = await oauth.issueRequestObject(as, client, parameters, jarPrivateKey, {
* [oauth.modifyAssertion](header, payload) {
* payload.exp = <number>payload.iat + 300
* },
Expand All @@ -343,7 +332,6 @@ export const modifyAssertion: unique symbol = Symbol()
* ```ts
* import * as jose from 'jose'
*
* // Prerequisites
* let as!: oauth.AuthorizationServer
* let key!: oauth.CryptoKey
* let alg!: string
Expand Down Expand Up @@ -404,7 +392,6 @@ export const jweDecrypt: unique symbol = Symbol()
* @example
*
* ```ts
* // Prerequisites
* let as!: oauth.AuthorizationServer
* let request!: Request
* let expectedAudience!: string
Expand Down Expand Up @@ -827,7 +814,6 @@ export interface Client {
* ```ts
* import * as undici from 'undici'
*
* // Prerequisites
* let as!: oauth.AuthorizationServer
* let client!: oauth.Client & { use_mtls_endpoint_aliases: true }
* let params!: URLSearchParams
Expand All @@ -850,7 +836,6 @@ export interface Client {
* Certificate-Bound Access Tokens support.
*
* ```ts
* // Prerequisites
* let as!: oauth.AuthorizationServer
* let client!: oauth.Client & { use_mtls_endpoint_aliases: true }
* let params!: URLSearchParams
Expand Down Expand Up @@ -1409,7 +1394,6 @@ export async function calculatePKCECodeChallenge(codeVerifier: string): Promise<
interface NormalizedKeyInput {
key?: CryptoKey
kid?: string
modifyAssertion?: ModifyAssertionFunction
}

function getKeyAndKid(input: CryptoKey | PrivateKey | undefined): NormalizedKeyInput {
Expand All @@ -1428,31 +1412,9 @@ function getKeyAndKid(input: CryptoKey | PrivateKey | undefined): NormalizedKeyI
return {
key: input.key,
kid: input.kid,
modifyAssertion: input[modifyAssertion],
}
}

export interface DPoPKeyPair extends CryptoKeyPair {
/**
* Private CryptoKey instance to sign the DPoP Proof JWT with.
*
* Its algorithm must be compatible with a supported {@link JWSAlgorithm JWS Algorithm}.
*/
privateKey: CryptoKey

/**
* The public key corresponding to {@link DPoPKeyPair.privateKey}.
*/
publicKey: CryptoKey

/**
* Use to modify the DPoP Proof JWT right before it is signed.
*
* @see {@link modifyAssertion}
*/
[modifyAssertion]?: ModifyAssertionFunction
}

export interface DPoPRequestOptions {
/**
* DPoP handle, obtained from {@link DPoP}
Expand Down Expand Up @@ -1658,6 +1620,15 @@ export function ClientSecretBasic(clientSecret: string): ClientAuth {
}
}

export interface ModifyAssertionOptions {
/**
* Use to modify a JWT assertion payload or header right before it is signed.
*
* @see {@link modifyAssertion}
*/
[modifyAssertion]?: ModifyAssertionFunction
}

/**
* **`private_key_jwt`** uses the HTTP request body to send `client_id`, `client_assertion_type`,
* and `client_assertion` as `application/x-www-form-urlencoded` body parameters. Digital signature
Expand All @@ -1670,8 +1641,11 @@ export function ClientSecretBasic(clientSecret: string): ClientAuth {
* @see [OAuth Token Endpoint Authentication Methods](https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#token-endpoint-auth-method)
* @see [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication)
*/
export function PrivateKeyJwt(clientPrivateKey: CryptoKey | PrivateKey): ClientAuth {
const { key, kid, modifyAssertion } = getKeyAndKid(clientPrivateKey)
export function PrivateKeyJwt(
clientPrivateKey: CryptoKey | PrivateKey,
options?: ModifyAssertionOptions,
): ClientAuth {
const { key, kid } = getKeyAndKid(clientPrivateKey)
assertPrivateKey(key, '"clientPrivateKey.key"')
return async (as, client, body, _headers) => {
const header = { alg: keyToJws(key), kid }
Expand All @@ -1686,7 +1660,7 @@ export function PrivateKeyJwt(clientPrivateKey: CryptoKey | PrivateKey): ClientA
sub: client.client_id,
}

modifyAssertion?.(header, payload)
options?.[modifyAssertion]?.(header, payload)

body.set('client_id', client.client_id)
body.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
Expand All @@ -1709,9 +1683,7 @@ export function PrivateKeyJwt(clientPrivateKey: CryptoKey | PrivateKey): ClientA
*/
export function ClientSecretJwt(
clientSecret: string,
options?: {
[modifyAssertion]?: ModifyAssertionFunction
},
options?: ModifyAssertionOptions,
): ClientAuth {
assertString(clientSecret, '"clientSecret"')
const modify = options?.[modifyAssertion]
Expand Down Expand Up @@ -1830,13 +1802,14 @@ export async function issueRequestObject(
client: Client,
parameters: URLSearchParams | Record<string, string> | string[][],
privateKey: CryptoKey | PrivateKey,
options?: ModifyAssertionOptions,
): Promise<string> {
assertAs(as)
assertClient(client)

parameters = new URLSearchParams(parameters)

const { key, kid, modifyAssertion } = getKeyAndKid(privateKey)
const { key, kid } = getKeyAndKid(privateKey)
assertPrivateKey(key, '"privateKey.key"')

parameters.set('client_id', client.client_id)
Expand Down Expand Up @@ -1916,7 +1889,7 @@ export async function issueRequestObject(
kid,
}

modifyAssertion?.(header, claims)
options?.[modifyAssertion]?.(header, claims)

return signJwt(header, claims, key)
}
Expand Down Expand Up @@ -2092,15 +2065,15 @@ class DPoPHandler implements DPoPHandle {
#modifyAssertion?: ModifyAssertionFunction
#map?: Map<string, string>

constructor(client: Client, keyPair: DPoPKeyPair) {
constructor(client: Client, keyPair: CryptoKeyPair, options?: ModifyAssertionOptions) {
assertPrivateKey(keyPair?.privateKey, '"DPoP.privateKey"')
assertPublicKey(keyPair?.publicKey, '"DPoP.publicKey"')

if (!keyPair.publicKey.extractable) {
throw CodedTypeError('"DPoP.publicKey.extractable" must be true', ERR_INVALID_ARG_VALUE)
}

this.#modifyAssertion = keyPair[modifyAssertion]
this.#modifyAssertion = options?.[modifyAssertion]
this.#clockSkew = getClockSkew(client)
this.#privateKey = keyPair.privateKey
this.#publicKey = keyPair.publicKey
Expand Down Expand Up @@ -2174,8 +2147,12 @@ class DPoPHandler implements DPoPHandle {
*
* @see {@link !DPoP RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession (DPoP)}
*/
export function DPoP(client: Client, keyPair: DPoPKeyPair): DPoPHandle {
return new DPoPHandler(client, keyPair)
export function DPoP(
client: Client,
keyPair: CryptoKeyPair,
options?: ModifyAssertionOptions,
): DPoPHandle {
return new DPoPHandler(client, keyPair, options)
}

export interface PushedAuthorizationResponse {
Expand Down
3 changes: 1 addition & 2 deletions tap/end2end-client-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ export default (QUnit: QUnit) => {
clientAuth = lib.ClientSecretJwt(client.client_secret as string)
break
case 'private_key_jwt':
clientAuth = lib.PrivateKeyJwt({
...clientPrivateKey,
clientAuth = lib.PrivateKeyJwt(clientPrivateKey, {
[lib.modifyAssertion](h, p) {
t.equal(h.alg, 'ES256')
p.foo = 'bar'
Expand Down
3 changes: 1 addition & 2 deletions tap/end2end-device-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ export default (QUnit: QUnit) => {
)
const DPoPKeyPair = await lib.generateKeyPair(alg)
const DPoP = dpop
? lib.DPoP(client, {
...DPoPKeyPair,
? lib.DPoP(client, DPoPKeyPair, {
[lib.modifyAssertion](h, p) {
t.equal(h.alg, 'ES256')
p.foo = 'bar'
Expand Down
2 changes: 1 addition & 1 deletion tap/request_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export default (QUnit: QUnit) => {
{
foo: 'bar',
},
kp.privateKey,
{
key: kp.privateKey,
[lib.modifyAssertion](h, p) {
t.equal(h.alg, 'ES256')
delete h.typ
Expand Down

0 comments on commit 4d8b9e8

Please sign in to comment.