oauth4webapi
Advanced tools
Comparing version 1.2.2 to 1.3.0
@@ -706,2 +706,27 @@ /** @ignore */ | ||
export declare const skipSubjectCheck: unique symbol; | ||
export interface SkipJWTSignatureCheckOptions { | ||
/** | ||
* DANGER ZONE | ||
* | ||
* When JWT assertions are received via direct communication between the Client and the | ||
* Token/UserInfo/Introspection endpoint (which they are in this library's supported profiles and | ||
* exposed functions) the TLS server validation MAY be used to validate the issuer in place of | ||
* checking the assertion's signature. | ||
* | ||
* Set this to `true` to omit verifying the JWT assertion's signature (e.g. ID Token, JWT Signed | ||
* Introspection, or JWT Signed UserInfo Response). | ||
* | ||
* Setting this to `true` also means that: | ||
* | ||
* - The Authorization Server's JSON Web Key Set will not be requested. That is useful for | ||
* javascript runtimes that execute on the edge and cannot reliably share an in-memory cache of | ||
* the JSON Web Key Set in between invocations. | ||
* - Any JWS Algorithm may be used, not just the {@link JWSAlgorithm supported ones}. | ||
* | ||
* Default is `false`. | ||
*/ | ||
skipJwtSignatureCheck?: boolean; | ||
} | ||
export interface ProcessUserInfoResponseOptions extends HttpRequestOptions, SkipJWTSignatureCheckOptions { | ||
} | ||
/** | ||
@@ -723,3 +748,3 @@ * Validates Response instance to be one coming from the | ||
*/ | ||
export declare function processUserInfoResponse(as: AuthorizationServer, client: Client, expectedSubject: string | typeof skipSubjectCheck, response: Response, options?: HttpRequestOptions): Promise<UserInfoResponse>; | ||
export declare function processUserInfoResponse(as: AuthorizationServer, client: Client, expectedSubject: string | typeof skipSubjectCheck, response: Response, options?: ProcessUserInfoResponseOptions): Promise<UserInfoResponse>; | ||
export interface TokenEndpointRequestOptions extends HttpRequestOptions, AuthenticatedRequestOptions, DPoPRequestOptions { | ||
@@ -760,2 +785,4 @@ /** Any additional parameters to send. This cannot override existing parameter values. */ | ||
export declare function getValidatedIdTokenClaims(ref: TokenEndpointResponse): IDToken | undefined; | ||
export interface ProcessRefreshTokenResponseOptions extends HttpRequestOptions, SkipJWTSignatureCheckOptions { | ||
} | ||
/** | ||
@@ -775,3 +802,3 @@ * Validates Refresh Token Grant Response instance to be one coming from the | ||
*/ | ||
export declare function processRefreshTokenResponse(as: AuthorizationServer, client: Client, response: Response, options?: HttpRequestOptions): Promise<TokenEndpointResponse | OAuth2Error>; | ||
export declare function processRefreshTokenResponse(as: AuthorizationServer, client: Client, response: Response, options?: ProcessRefreshTokenResponseOptions): Promise<TokenEndpointResponse | OAuth2Error>; | ||
/** | ||
@@ -863,2 +890,4 @@ * Performs an Authorization Code grant request at the | ||
export declare const skipAuthTimeCheck: unique symbol; | ||
export interface ProcessAuthorizationCodeOpenIDResponseOptions extends HttpRequestOptions, SkipJWTSignatureCheckOptions { | ||
} | ||
/** | ||
@@ -884,3 +913,3 @@ * (OpenID Connect only) Validates Authorization Code Grant Response instance to be one coming from | ||
*/ | ||
export declare function processAuthorizationCodeOpenIDResponse(as: AuthorizationServer, client: Client, response: Response, expectedNonce?: string | typeof expectNoNonce, maxAge?: number | typeof skipAuthTimeCheck, options?: HttpRequestOptions): Promise<OpenIDTokenEndpointResponse | OAuth2Error>; | ||
export declare function processAuthorizationCodeOpenIDResponse(as: AuthorizationServer, client: Client, response: Response, expectedNonce?: string | typeof expectNoNonce, maxAge?: number | typeof skipAuthTimeCheck, options?: ProcessAuthorizationCodeOpenIDResponseOptions): Promise<OpenIDTokenEndpointResponse | OAuth2Error>; | ||
/** | ||
@@ -1000,2 +1029,4 @@ * (OAuth 2.0 without OpenID Connect only) Validates Authorization Code Grant Response instance to | ||
} | ||
export interface ProcessIntrospectionResponseOptions extends HttpRequestOptions, SkipJWTSignatureCheckOptions { | ||
} | ||
/** | ||
@@ -1015,3 +1046,3 @@ * Validates Response instance to be one coming from the | ||
*/ | ||
export declare function processIntrospectionResponse(as: AuthorizationServer, client: Client, response: Response, options?: HttpRequestOptions): Promise<IntrospectionResponse | OAuth2Error>; | ||
export declare function processIntrospectionResponse(as: AuthorizationServer, client: Client, response: Response, options?: ProcessIntrospectionResponseOptions): Promise<IntrospectionResponse | OAuth2Error>; | ||
/** @ignore */ | ||
@@ -1151,2 +1182,4 @@ export interface JwksRequestOptions extends HttpRequestOptions { | ||
export declare function deviceCodeGrantRequest(as: AuthorizationServer, client: Client, deviceCode: string, options?: TokenEndpointRequestOptions): Promise<Response>; | ||
export interface ProcessDeviceCodeResponseOptions extends HttpRequestOptions, SkipJWTSignatureCheckOptions { | ||
} | ||
/** | ||
@@ -1165,3 +1198,3 @@ * Validates Device Authorization Grant Response instance to be one coming from the | ||
*/ | ||
export declare function processDeviceCodeResponse(as: AuthorizationServer, client: Client, response: Response, options?: HttpRequestOptions): Promise<TokenEndpointResponse | OAuth2Error>; | ||
export declare function processDeviceCodeResponse(as: AuthorizationServer, client: Client, response: Response, options?: ProcessDeviceCodeResponseOptions): Promise<TokenEndpointResponse | OAuth2Error>; | ||
export interface GenerateKeyPairOptions { | ||
@@ -1168,0 +1201,0 @@ /** Indicates whether or not the private key may be exported. Default is `false`. */ |
let USER_AGENT; | ||
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) { | ||
const NAME = 'oauth4webapi'; | ||
const VERSION = 'v1.2.2'; | ||
const VERSION = 'v1.3.0'; | ||
USER_AGENT = `${NAME}/${VERSION}`; | ||
@@ -735,6 +735,5 @@ } | ||
if (getContentType(response) === 'application/jwt') { | ||
if (typeof as.jwks_uri !== 'string') { | ||
throw new TypeError('"as.jwks_uri" must be a string'); | ||
} | ||
const { claims } = await validateJwt(await preserveBodyStream(response).text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options)) | ||
const { claims } = await validateJwt(await preserveBodyStream(response).text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), options?.skipJwtSignatureCheck !== true | ||
? getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options) | ||
: noSignatureCheck) | ||
.then(validateOptionalAudience.bind(undefined, client.client_id)) | ||
@@ -774,39 +773,2 @@ .then(validateOptionalIssuer.bind(undefined, as.issuer)); | ||
} | ||
function padded(buf, length) { | ||
const out = new Uint8Array(length); | ||
out.set(buf); | ||
return out; | ||
} | ||
function timingSafeEqual(a, b) { | ||
const len = Math.max(a.byteLength, b.byteLength); | ||
a = padded(a, len); | ||
b = padded(b, len); | ||
let out = 0; | ||
let i = -1; | ||
while (++i < len) { | ||
out |= a[i] ^ b[i]; | ||
} | ||
return out === 0; | ||
} | ||
async function idTokenHash(alg, data) { | ||
let algorithm; | ||
switch (alg) { | ||
case 'RS256': | ||
case 'PS256': | ||
case 'ES256': | ||
algorithm = { name: 'SHA-256' }; | ||
break; | ||
case 'EdDSA': | ||
algorithm = { name: 'SHA-512' }; | ||
break; | ||
default: | ||
throw new UnsupportedOperationError(); | ||
} | ||
const digest = await crypto.subtle.digest(algorithm, buf(data)); | ||
return b64u(digest.slice(0, digest.byteLength / 2)); | ||
} | ||
async function idTokenHashMatches(alg, data, actual) { | ||
const expected = await idTokenHash(alg, data); | ||
return timingSafeEqual(buf(actual), buf(expected)); | ||
} | ||
async function authenticatedRequest(as, client, method, url, body, headers, options) { | ||
@@ -852,3 +814,3 @@ await clientAuthentication(as, client, body, headers, options?.clientPrivateKey); | ||
} | ||
async function processGenericAccessTokenResponse(as, client, response, options, ignoreIdToken = false, ignoreRefreshToken = false) { | ||
async function processGenericAccessTokenResponse(as, client, response, options, ignoreIdToken = false, ignoreRefreshToken = false, skipSignatureCheck = false) { | ||
assertAs(as); | ||
@@ -903,6 +865,5 @@ assertClient(client); | ||
if (json.id_token) { | ||
if (typeof as.jwks_uri !== 'string') { | ||
throw new TypeError('"as.jwks_uri" must be a string'); | ||
} | ||
const { header, claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options)) | ||
const { claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), skipSignatureCheck !== true | ||
? getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options) | ||
: noSignatureCheck) | ||
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iat', 'iss', 'sub'])) | ||
@@ -917,8 +878,2 @@ .then(validateIssuer.bind(undefined, as.issuer)) | ||
} | ||
if (claims.at_hash !== undefined) { | ||
if (typeof claims.at_hash !== 'string' || | ||
!(await idTokenHashMatches(header.alg, json.access_token, claims.at_hash))) { | ||
throw new OPE('unexpected ID Token "at_hash" (access token hash) claim value'); | ||
} | ||
} | ||
idTokenClaims.set(json, claims); | ||
@@ -930,3 +885,3 @@ } | ||
export async function processRefreshTokenResponse(as, client, response, options) { | ||
return processGenericAccessTokenResponse(as, client, response, options); | ||
return processGenericAccessTokenResponse(as, client, response, options, undefined, undefined, options?.skipJwtSignatureCheck === true); | ||
} | ||
@@ -1002,3 +957,3 @@ function validateOptionalAudience(expected, result) { | ||
export async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, options) { | ||
const result = await processGenericAccessTokenResponse(as, client, response, options); | ||
const result = await processGenericAccessTokenResponse(as, client, response, options, undefined, undefined, options?.skipJwtSignatureCheck === true); | ||
if (isOAuth2Error(result)) { | ||
@@ -1147,6 +1102,5 @@ return result; | ||
if (getContentType(response) === 'application/token-introspection+jwt') { | ||
if (typeof as.jwks_uri !== 'string') { | ||
throw new TypeError('"as.jwks_uri" must be a string'); | ||
} | ||
const { claims } = await validateJwt(await preserveBodyStream(response).text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options)) | ||
const { claims } = await validateJwt(await preserveBodyStream(response).text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), options?.skipJwtSignatureCheck !== true | ||
? getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options) | ||
: noSignatureCheck) | ||
.then(checkJwtType.bind(undefined, 'token-introspection+jwt')) | ||
@@ -1271,4 +1225,5 @@ .then(validatePresence.bind(undefined, ['aud', 'iat', 'iss'])) | ||
} | ||
const noSignatureCheck = Symbol(); | ||
async function validateJwt(jws, checkAlg, getKey) { | ||
const { 0: protectedHeader, 1: payload, 2: signature, length } = jws.split('.'); | ||
const { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.'); | ||
if (length === 5) { | ||
@@ -1294,7 +1249,10 @@ throw new UnsupportedOperationError('JWE structure JWTs are not supported'); | ||
} | ||
const key = await getKey(header); | ||
const input = `${protectedHeader}.${payload}`; | ||
const verified = await crypto.subtle.verify(subtleAlgorithm(key), key, b64u(signature), buf(input)); | ||
if (!verified) { | ||
throw new OPE('JWT signature verification failed'); | ||
const signature = b64u(encodedSignature); | ||
if (getKey !== noSignatureCheck) { | ||
const key = await getKey(header); | ||
const input = `${protectedHeader}.${payload}`; | ||
const verified = await crypto.subtle.verify(subtleAlgorithm(key), key, signature, buf(input)); | ||
if (!verified) { | ||
throw new OPE('JWT signature verification failed'); | ||
} | ||
} | ||
@@ -1344,3 +1302,3 @@ let claims; | ||
} | ||
return { header, claims }; | ||
return { header, claims, signature }; | ||
} | ||
@@ -1550,3 +1508,3 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedState, options) { | ||
export async function processDeviceCodeResponse(as, client, response, options) { | ||
return processGenericAccessTokenResponse(as, client, response, options); | ||
return processGenericAccessTokenResponse(as, client, response, options, undefined, undefined, options?.skipJwtSignatureCheck === true); | ||
} | ||
@@ -1553,0 +1511,0 @@ export async function generateKeyPair(alg, options) { |
{ | ||
"name": "oauth4webapi", | ||
"version": "1.2.2", | ||
"version": "1.3.0", | ||
"description": "OAuth 2 / OpenID Connect for Web Platform API JavaScript runtimes", | ||
@@ -13,7 +13,10 @@ "keywords": [ | ||
"client", | ||
"cloudflare-workers", | ||
"cloudflare", | ||
"deno", | ||
"edge", | ||
"electron", | ||
"fapi", | ||
"javascript", | ||
"netlify", | ||
"next", | ||
"nextjs", | ||
@@ -27,3 +30,4 @@ "node", | ||
"openid", | ||
"vercel-edge" | ||
"vercel", | ||
"workers" | ||
], | ||
@@ -62,20 +66,20 @@ "homepage": "https://github.com/panva/oauth4webapi", | ||
"@esbuild-kit/esm-loader": "^2.5.0", | ||
"@types/node": "^18.11.2", | ||
"@types/node": "^18.11.7", | ||
"@types/qunit": "^2.19.3", | ||
"ava": "^4.3.3", | ||
"edge-runtime": "^1.1.0-beta.40", | ||
"jose": "^4.10.0", | ||
"patch-package": "^6.4.7", | ||
"ava": "^5.0.1", | ||
"edge-runtime": "^2.0.0", | ||
"jose": "^4.10.4", | ||
"patch-package": "^6.5.0", | ||
"prettier": "^2.7.1", | ||
"prettier-plugin-jsdoc": "^0.4.2", | ||
"qunit": "^2.19.2", | ||
"qunit": "^2.19.3", | ||
"testcafe": "^2.0.1", | ||
"testcafe-browser-provider-browserstack": "^1.14.0", | ||
"timekeeper": "^2.2.0", | ||
"typedoc": "^0.23.17", | ||
"typedoc": "^0.23.19", | ||
"typedoc-plugin-markdown": "^3.13.6", | ||
"typescript": "^4.8.4", | ||
"undici": "^5.11.0", | ||
"undici": "^5.12.0", | ||
"workerd": "^1.20220926.3" | ||
} | ||
} |
@@ -55,22 +55,14 @@ # OAuth 2 / OpenID Connect for Web Platform API JavaScript runtimes | ||
## Runtime requirements | ||
## Supported Runtimes | ||
The supported JavaScript runtimes include ones that | ||
The supported JavaScript runtimes include ones that support the utilized Web API globals and standard built-in objects | ||
- are reasonably up to date ECMAScript (targets ES2020, but may be further transpiled for compatibility) | ||
- support required Web API globals and standard built-in objects | ||
- [Fetch API][] and its related globals [fetch][], [Response][], [Headers][] | ||
- [Web Crypto API][] and its related globals [crypto][], [CryptoKey][] | ||
- [Encoding API][] and its related globals [TextEncoder][], [TextDecoder][] | ||
- [URL API][] and its related globals [URL][], [URLSearchParams][] | ||
- [atob][] and [btoa][] | ||
- [Uint8Array][] | ||
- These are (not an exhaustive list): | ||
- Browsers | ||
- Cloudflare Workers | ||
- Deno | ||
- Electron | ||
- Next.js Middlewares | ||
- Node.js ([runtime flags may be needed](https://github.com/panva/oauth4webapi/issues/8)) | ||
- Vercel Edge Functions | ||
These are _(this is not an exhaustive list)_: | ||
- Browsers | ||
- Cloudflare Workers | ||
- Deno | ||
- Electron | ||
- Netlify Edge | ||
- Node.js ([runtime flags may be needed](https://github.com/panva/oauth4webapi/issues/8)) | ||
- Vercel's Edge Runtime | ||
@@ -85,18 +77,1 @@ ## Out of scope | ||
- Automatic polyfills of any kind | ||
[web crypto api]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API | ||
[fetch api]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | ||
[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/fetch | ||
[textdecoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder | ||
[textencoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder | ||
[btoa]: https://developer.mozilla.org/en-US/docs/Web/API/btoa | ||
[atob]: https://developer.mozilla.org/en-US/docs/Web/API/atob | ||
[uint8array]: https://developer.mozilla.org/en-US/docs/Web/API/Uint8Array | ||
[response]: https://developer.mozilla.org/en-US/docs/Web/API/Response | ||
[headers]: https://developer.mozilla.org/en-US/docs/Web/API/Headers | ||
[crypto]: https://developer.mozilla.org/en-US/docs/Web/API/crypto | ||
[cryptokey]: https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey | ||
[urlsearchparams]: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams | ||
[encoding api]: https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API | ||
[url api]: https://developer.mozilla.org/en-US/docs/Web/API/URL_API | ||
[url]: https://developer.mozilla.org/en-US/docs/Web/API/URL |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
124385
2766
76