@auth0/auth0-spa-js 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auth0-spa-js.development.js +600 -14
- package/dist/auth0-spa-js.development.js.map +1 -1
- package/dist/auth0-spa-js.production.esm.js +1 -1
- package/dist/auth0-spa-js.production.esm.js.map +1 -1
- package/dist/auth0-spa-js.production.js +1 -1
- package/dist/auth0-spa-js.production.js.map +1 -1
- package/dist/auth0-spa-js.worker.development.js +10 -2
- package/dist/auth0-spa-js.worker.development.js.map +1 -1
- package/dist/auth0-spa-js.worker.production.js +1 -1
- package/dist/auth0-spa-js.worker.production.js.map +1 -1
- package/dist/lib/auth0-spa-js.cjs.js +641 -14
- package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
- package/dist/typings/Auth0Client.d.ts +50 -0
- package/dist/typings/Auth0Client.utils.d.ts +1 -1
- package/dist/typings/api.d.ts +1 -1
- package/dist/typings/cache/shared.d.ts +1 -0
- package/dist/typings/dpop/dpop.d.ts +17 -0
- package/dist/typings/dpop/storage.d.ts +27 -0
- package/dist/typings/dpop/utils.d.ts +15 -0
- package/dist/typings/errors.d.ts +7 -0
- package/dist/typings/fetcher.d.ts +48 -0
- package/dist/typings/global.d.ts +19 -0
- package/dist/typings/http.d.ts +2 -1
- package/dist/typings/index.d.ts +2 -1
- package/dist/typings/utils.d.ts +6 -0
- package/dist/typings/version.d.ts +1 -1
- package/package.json +22 -19
- package/src/Auth0Client.ts +112 -5
- package/src/Auth0Client.utils.ts +4 -2
- package/src/api.ts +6 -1
- package/src/cache/shared.ts +1 -0
- package/src/dpop/dpop.ts +56 -0
- package/src/dpop/storage.ts +134 -0
- package/src/dpop/utils.ts +66 -0
- package/src/errors.ts +11 -0
- package/src/fetcher.ts +224 -0
- package/src/global.ts +21 -0
- package/src/http.ts +70 -5
- package/src/index.ts +4 -1
- package/src/utils.ts +15 -0
- package/src/version.ts +1 -1
- package/src/worker/token.worker.ts +11 -5
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Auth0ClientOptions, RedirectLoginOptions, PopupLoginOptions, PopupConfigOptions, RedirectLoginResult, GetTokenSilentlyOptions, GetTokenWithPopupOptions, LogoutOptions, User, IdToken, GetTokenSilentlyVerboseResponse, TokenEndpointResponse } from './global';
|
|
2
2
|
import { CustomTokenExchangeOptions } from './TokenExchange';
|
|
3
|
+
import { Dpop } from './dpop/dpop';
|
|
4
|
+
import { Fetcher, type FetcherConfig, type CustomFetchMinimalOutput } from './fetcher';
|
|
3
5
|
/**
|
|
4
6
|
* Auth0 SDK for Single Page Applications using [Authorization Code Grant Flow with PKCE](https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce).
|
|
5
7
|
*/
|
|
@@ -10,6 +12,7 @@ export declare class Auth0Client {
|
|
|
10
12
|
private readonly tokenIssuer;
|
|
11
13
|
private readonly scope;
|
|
12
14
|
private readonly cookieStorage;
|
|
15
|
+
private readonly dpop;
|
|
13
16
|
private readonly sessionCheckExpiryDays;
|
|
14
17
|
private readonly orgHintCookieName;
|
|
15
18
|
private readonly isAuthenticatedCookieName;
|
|
@@ -226,4 +229,51 @@ export declare class Auth0Client {
|
|
|
226
229
|
* ```
|
|
227
230
|
*/
|
|
228
231
|
exchangeToken(options: CustomTokenExchangeOptions): Promise<TokenEndpointResponse>;
|
|
232
|
+
protected _assertDpop(dpop: Dpop | undefined): asserts dpop is Dpop;
|
|
233
|
+
/**
|
|
234
|
+
* Returns the current DPoP nonce used for making requests to Auth0.
|
|
235
|
+
*
|
|
236
|
+
* It can return `undefined` because when starting fresh it will not
|
|
237
|
+
* be populated until after the first response from the server.
|
|
238
|
+
*
|
|
239
|
+
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
|
|
240
|
+
*
|
|
241
|
+
* @param nonce The nonce value.
|
|
242
|
+
* @param id The identifier of a nonce: if absent, it will get the nonce
|
|
243
|
+
* used for requests to Auth0. Otherwise, it will be used to
|
|
244
|
+
* select a specific non-Auth0 nonce.
|
|
245
|
+
*/
|
|
246
|
+
getDpopNonce(id?: string): Promise<string | undefined>;
|
|
247
|
+
/**
|
|
248
|
+
* Sets the current DPoP nonce used for making requests to Auth0.
|
|
249
|
+
*
|
|
250
|
+
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
|
|
251
|
+
*
|
|
252
|
+
* @param nonce The nonce value.
|
|
253
|
+
* @param id The identifier of a nonce: if absent, it will set the nonce
|
|
254
|
+
* used for requests to Auth0. Otherwise, it will be used to
|
|
255
|
+
* select a specific non-Auth0 nonce.
|
|
256
|
+
*/
|
|
257
|
+
setDpopNonce(nonce: string, id?: string): Promise<void>;
|
|
258
|
+
/**
|
|
259
|
+
* Returns a string to be used to demonstrate possession of the private
|
|
260
|
+
* key used to cryptographically bind access tokens with DPoP.
|
|
261
|
+
*
|
|
262
|
+
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
|
|
263
|
+
*/
|
|
264
|
+
generateDpopProof(params: {
|
|
265
|
+
url: string;
|
|
266
|
+
method: string;
|
|
267
|
+
nonce?: string;
|
|
268
|
+
accessToken: string;
|
|
269
|
+
}): Promise<string>;
|
|
270
|
+
/**
|
|
271
|
+
* Returns a new `Fetcher` class that will contain a `fetchWithAuth()` method.
|
|
272
|
+
* This is a drop-in replacement for the Fetch API's `fetch()` method, but will
|
|
273
|
+
* handle certain authentication logic for you, like building the proper auth
|
|
274
|
+
* headers or managing DPoP nonces and retries automatically.
|
|
275
|
+
*
|
|
276
|
+
* Check the `EXAMPLES.md` file for a deeper look into this method.
|
|
277
|
+
*/
|
|
278
|
+
createFetcher<TOutput extends CustomFetchMinimalOutput = Response>(config?: FetcherConfig<TOutput>): Fetcher<TOutput>;
|
|
229
279
|
}
|
|
@@ -25,7 +25,7 @@ export declare const cacheFactory: (location: string) => () => ICache;
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const getAuthorizeParams: (clientOptions: Auth0ClientOptions & {
|
|
27
27
|
authorizationParams: AuthorizationParams;
|
|
28
|
-
}, scope: string, authorizationParams: AuthorizationParams, state: string, nonce: string, code_challenge: string, redirect_uri: string | undefined, response_mode: string | undefined) => AuthorizeOptions;
|
|
28
|
+
}, scope: string, authorizationParams: AuthorizationParams, state: string, nonce: string, code_challenge: string, redirect_uri: string | undefined, response_mode: string | undefined, thumbprint: string | undefined) => AuthorizeOptions;
|
|
29
29
|
/**
|
|
30
30
|
* @ignore
|
|
31
31
|
*
|
package/dist/typings/api.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { TokenEndpointOptions, TokenEndpointResponse } from './global';
|
|
2
|
-
export declare function oauthToken({ baseUrl, timeout, audience, scope, auth0Client, useFormData, ...options }: TokenEndpointOptions, worker?: Worker): Promise<TokenEndpointResponse>;
|
|
2
|
+
export declare function oauthToken({ baseUrl, timeout, audience, scope, auth0Client, useFormData, dpop, ...options }: TokenEndpointOptions, worker?: Worker): Promise<TokenEndpointResponse>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DpopStorage } from './storage';
|
|
2
|
+
import * as dpopUtils from './utils';
|
|
3
|
+
export declare class Dpop {
|
|
4
|
+
protected readonly storage: DpopStorage;
|
|
5
|
+
constructor(clientId: string);
|
|
6
|
+
getNonce(id?: string): Promise<string | undefined>;
|
|
7
|
+
setNonce(nonce: string, id?: string): Promise<void>;
|
|
8
|
+
protected getOrGenerateKeyPair(): Promise<dpopUtils.KeyPair>;
|
|
9
|
+
generateProof(params: {
|
|
10
|
+
url: string;
|
|
11
|
+
method: string;
|
|
12
|
+
nonce?: string;
|
|
13
|
+
accessToken?: string;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
calculateThumbprint(): Promise<string>;
|
|
16
|
+
clear(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type KeyPair } from './utils';
|
|
2
|
+
declare const TABLES: {
|
|
3
|
+
readonly NONCE: "nonce";
|
|
4
|
+
readonly KEYPAIR: "keypair";
|
|
5
|
+
};
|
|
6
|
+
type Table = (typeof TABLES)[keyof typeof TABLES];
|
|
7
|
+
export declare class DpopStorage {
|
|
8
|
+
protected readonly clientId: string;
|
|
9
|
+
protected dbHandle: IDBDatabase | undefined;
|
|
10
|
+
constructor(clientId: string);
|
|
11
|
+
protected getVersion(): number;
|
|
12
|
+
protected createDbHandle(): Promise<IDBDatabase>;
|
|
13
|
+
protected getDbHandle(): Promise<IDBDatabase>;
|
|
14
|
+
protected executeDbRequest<T = unknown>(table: string, mode: IDBTransactionMode, requestFactory: (table: IDBObjectStore) => IDBRequest<T>): Promise<T>;
|
|
15
|
+
protected buildKey(id?: string): string;
|
|
16
|
+
setNonce(nonce: string, id?: string): Promise<void>;
|
|
17
|
+
setKeyPair(keyPair: KeyPair): Promise<void>;
|
|
18
|
+
protected save(table: Table, key: IDBValidKey, obj: unknown): Promise<void>;
|
|
19
|
+
findNonce(id?: string): Promise<string | undefined>;
|
|
20
|
+
findKeyPair(): Promise<KeyPair | undefined>;
|
|
21
|
+
protected find<T = unknown>(table: Table, key: IDBValidKey): Promise<T | undefined>;
|
|
22
|
+
protected deleteBy(table: Table, predicate: (key: IDBValidKey) => boolean): Promise<void>;
|
|
23
|
+
protected deleteByClientId(table: Table, clientId: string): Promise<void>;
|
|
24
|
+
clearNonces(): Promise<void>;
|
|
25
|
+
clearKeyPairs(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as dpopLib from 'dpop';
|
|
2
|
+
export declare const DPOP_NONCE_HEADER = "dpop-nonce";
|
|
3
|
+
export type KeyPair = Readonly<dpopLib.KeyPair>;
|
|
4
|
+
type GenerateProofParams = {
|
|
5
|
+
keyPair: KeyPair;
|
|
6
|
+
url: string;
|
|
7
|
+
method: string;
|
|
8
|
+
nonce?: string;
|
|
9
|
+
accessToken?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function generateKeyPair(): Promise<KeyPair>;
|
|
12
|
+
export declare function calculateThumbprint(keyPair: Pick<KeyPair, 'publicKey'>): Promise<string>;
|
|
13
|
+
export declare function generateProof({ keyPair, url, method, nonce, accessToken }: GenerateProofParams): Promise<string>;
|
|
14
|
+
export declare function isGrantTypeSupported(grantType: string): boolean;
|
|
15
|
+
export {};
|
package/dist/typings/errors.d.ts
CHANGED
|
@@ -52,3 +52,10 @@ export declare class MissingRefreshTokenError extends GenericError {
|
|
|
52
52
|
scope: string;
|
|
53
53
|
constructor(audience: string, scope: string);
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Error thrown when the wrong DPoP nonce is used and a potential subsequent retry wasn't able to fix it.
|
|
57
|
+
*/
|
|
58
|
+
export declare class UseDpopNonceError extends GenericError {
|
|
59
|
+
newDpopNonce: string | undefined;
|
|
60
|
+
constructor(newDpopNonce: string | undefined);
|
|
61
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type ResponseHeaders = Record<string, string | null | undefined> | [string, string][] | {
|
|
2
|
+
get(name: string): string | null | undefined;
|
|
3
|
+
};
|
|
4
|
+
export type CustomFetchMinimalOutput = {
|
|
5
|
+
status: number;
|
|
6
|
+
headers: ResponseHeaders;
|
|
7
|
+
};
|
|
8
|
+
export type CustomFetchImpl<TOutput extends CustomFetchMinimalOutput> = (req: Request) => Promise<TOutput>;
|
|
9
|
+
type AccessTokenFactory = () => Promise<string>;
|
|
10
|
+
export type FetcherConfig<TOutput extends CustomFetchMinimalOutput> = {
|
|
11
|
+
getAccessToken?: AccessTokenFactory;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
fetch?: CustomFetchImpl<TOutput>;
|
|
14
|
+
dpopNonceId?: string;
|
|
15
|
+
};
|
|
16
|
+
export type FetcherHooks = {
|
|
17
|
+
isDpopEnabled: () => boolean;
|
|
18
|
+
getAccessToken: () => Promise<string>;
|
|
19
|
+
getDpopNonce: () => Promise<string | undefined>;
|
|
20
|
+
setDpopNonce: (nonce: string) => Promise<void>;
|
|
21
|
+
generateDpopProof: (params: {
|
|
22
|
+
url: string;
|
|
23
|
+
method: string;
|
|
24
|
+
nonce?: string;
|
|
25
|
+
accessToken: string;
|
|
26
|
+
}) => Promise<string>;
|
|
27
|
+
};
|
|
28
|
+
export type FetchWithAuthCallbacks<TOutput> = {
|
|
29
|
+
onUseDpopNonceError?(): Promise<TOutput>;
|
|
30
|
+
};
|
|
31
|
+
export declare class Fetcher<TOutput extends CustomFetchMinimalOutput> {
|
|
32
|
+
protected readonly config: Omit<FetcherConfig<TOutput>, 'fetch'> & Required<Pick<FetcherConfig<TOutput>, 'fetch'>>;
|
|
33
|
+
protected readonly hooks: FetcherHooks;
|
|
34
|
+
constructor(config: FetcherConfig<TOutput>, hooks: FetcherHooks);
|
|
35
|
+
protected isAbsoluteUrl(url: string): boolean;
|
|
36
|
+
protected buildUrl(baseUrl: string | undefined, url: string | undefined): string;
|
|
37
|
+
protected getAccessToken(): Promise<string>;
|
|
38
|
+
protected buildBaseRequest(info: RequestInfo | URL, init: RequestInit | undefined): Request;
|
|
39
|
+
protected setAuthorizationHeader(request: Request, accessToken: string): Promise<void>;
|
|
40
|
+
protected setDpopProofHeader(request: Request, accessToken: string): Promise<void>;
|
|
41
|
+
protected prepareRequest(request: Request): Promise<void>;
|
|
42
|
+
protected getHeader(headers: ResponseHeaders, name: string): string;
|
|
43
|
+
protected hasUseDpopNonceError(response: TOutput): boolean;
|
|
44
|
+
protected handleResponse(response: TOutput, callbacks: FetchWithAuthCallbacks<TOutput>): Promise<TOutput>;
|
|
45
|
+
protected internalFetchWithAuth(info: RequestInfo | URL, init: RequestInit | undefined, callbacks: FetchWithAuthCallbacks<TOutput>): Promise<TOutput>;
|
|
46
|
+
fetchWithAuth(info: RequestInfo | URL, init?: RequestInit): Promise<TOutput>;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
package/dist/typings/global.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ICache } from './cache';
|
|
2
|
+
import type { Dpop } from './dpop/dpop';
|
|
2
3
|
export interface AuthorizationParams {
|
|
3
4
|
/**
|
|
4
5
|
* - `'page'`: displays the UI with a full page view
|
|
@@ -242,6 +243,14 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
|
|
|
242
243
|
* **Note**: The worker is only used when `useRefreshTokens: true`, `cacheLocation: 'memory'`, and the `cache` is not custom.
|
|
243
244
|
*/
|
|
244
245
|
workerUrl?: string;
|
|
246
|
+
/**
|
|
247
|
+
* If `true`, DPoP (OAuth 2.0 Demonstrating Proof of Possession, RFC9449)
|
|
248
|
+
* will be used to cryptographically bind tokens to this specific browser
|
|
249
|
+
* so they can't be used from a different device in case of a leak.
|
|
250
|
+
*
|
|
251
|
+
* The default setting is `false`.
|
|
252
|
+
*/
|
|
253
|
+
useDpop?: boolean;
|
|
245
254
|
}
|
|
246
255
|
/**
|
|
247
256
|
* The possible locations where tokens can be stored
|
|
@@ -475,10 +484,12 @@ export interface TokenEndpointOptions {
|
|
|
475
484
|
timeout?: number;
|
|
476
485
|
auth0Client: any;
|
|
477
486
|
useFormData?: boolean;
|
|
487
|
+
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>;
|
|
478
488
|
[key: string]: any;
|
|
479
489
|
}
|
|
480
490
|
export type TokenEndpointResponse = {
|
|
481
491
|
id_token: string;
|
|
492
|
+
token_type: string;
|
|
482
493
|
access_token: string;
|
|
483
494
|
refresh_token?: string;
|
|
484
495
|
expires_in: number;
|
|
@@ -587,5 +598,13 @@ export type FetchOptions = {
|
|
|
587
598
|
body?: string;
|
|
588
599
|
signal?: AbortSignal;
|
|
589
600
|
};
|
|
601
|
+
/**
|
|
602
|
+
* @ignore
|
|
603
|
+
*/
|
|
604
|
+
export type FetchResponse = {
|
|
605
|
+
ok: boolean;
|
|
606
|
+
headers: Record<string, string | undefined>;
|
|
607
|
+
json: any;
|
|
608
|
+
};
|
|
590
609
|
export type GetTokenSilentlyVerboseResponse = Omit<TokenEndpointResponse, 'refresh_token'>;
|
|
591
610
|
export {};
|
package/dist/typings/http.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FetchOptions } from './global';
|
|
2
|
+
import { Dpop } from './dpop/dpop';
|
|
2
3
|
export declare const createAbortController: () => AbortController;
|
|
3
4
|
export declare const switchFetch: (fetchUrl: string, audience: string, scope: string, fetchOptions: FetchOptions, worker?: Worker, useFormData?: boolean, timeout?: number) => Promise<any>;
|
|
4
|
-
export declare function getJSON<T>(url: string, timeout: number | undefined, audience: string, scope: string, options: FetchOptions, worker?: Worker, useFormData?: boolean): Promise<T>;
|
|
5
|
+
export declare function getJSON<T>(url: string, timeout: number | undefined, audience: string, scope: string, options: FetchOptions, worker?: Worker, useFormData?: boolean, dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>, isDpopRetry?: boolean): Promise<T>;
|
package/dist/typings/index.d.ts
CHANGED
|
@@ -13,5 +13,6 @@ export * from './global';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function createAuth0Client(options: Auth0ClientOptions): Promise<Auth0Client>;
|
|
15
15
|
export { Auth0Client };
|
|
16
|
-
export { GenericError, AuthenticationError, TimeoutError, PopupTimeoutError, PopupCancelledError, MfaRequiredError, MissingRefreshTokenError } from './errors';
|
|
16
|
+
export { GenericError, AuthenticationError, TimeoutError, PopupTimeoutError, PopupCancelledError, MfaRequiredError, MissingRefreshTokenError, UseDpopNonceError } from './errors';
|
|
17
17
|
export { ICache, LocalStorageCache, InMemoryCache, Cacheable, DecodedToken, CacheEntry, WrappedCacheEntry, KeyManifestEntry, MaybePromise, CacheKey, CacheKeyData } from './cache';
|
|
18
|
+
export { type FetcherConfig } from './fetcher';
|
package/dist/typings/utils.d.ts
CHANGED
|
@@ -21,3 +21,9 @@ export declare const getDomain: (domainUrl: string) => string;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare const getTokenIssuer: (issuer: string | undefined, domainUrl: string) => string;
|
|
23
23
|
export declare const parseNumber: (value: any) => number | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Ponyfill for `Object.fromEntries()`, which is not available until ES2020.
|
|
26
|
+
*
|
|
27
|
+
* When the target of this project reaches ES2020, this can be removed.
|
|
28
|
+
*/
|
|
29
|
+
export declare const fromEntries: <T = any>(iterable: Iterable<[PropertyKey, T]>) => Record<PropertyKey, T>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "2.
|
|
1
|
+
declare const _default: "2.4.0";
|
|
2
2
|
export default _default;
|
package/package.json
CHANGED
|
@@ -3,16 +3,32 @@
|
|
|
3
3
|
"name": "@auth0/auth0-spa-js",
|
|
4
4
|
"description": "Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.4.0",
|
|
7
7
|
"main": "dist/lib/auth0-spa-js.cjs.js",
|
|
8
8
|
"types": "dist/typings/index.d.ts",
|
|
9
9
|
"module": "dist/auth0-spa-js.production.esm.js",
|
|
10
|
+
"ccu": {
|
|
11
|
+
"name": "auth0-spa-js",
|
|
12
|
+
"cdn": "https://cdn.auth0.com",
|
|
13
|
+
"bucket": "assets.us.auth0.com",
|
|
14
|
+
"localPath": "dist",
|
|
15
|
+
"mainBundleFile": "auth0-spa-js.production.js",
|
|
16
|
+
"digest": {
|
|
17
|
+
"hashes": [
|
|
18
|
+
"sha384"
|
|
19
|
+
],
|
|
20
|
+
"extensions": [
|
|
21
|
+
".js"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
10
25
|
"scripts": {
|
|
11
26
|
"dev": "rimraf dist && rollup -c --watch",
|
|
12
27
|
"start": "npm run dev",
|
|
13
28
|
"docs": "typedoc --options ./typedoc.js src",
|
|
14
29
|
"build": "rimraf dist && rollup -m -c --environment NODE_ENV:production && npm run test:es-check",
|
|
15
30
|
"build:stats": "rimraf dist && rollup -m -c --environment NODE_ENV:production --environment WITH_STATS:true && npm run test:es-check && open bundle-stats/index.html",
|
|
31
|
+
"lint": "eslint --ext .jsx,.js src/",
|
|
16
32
|
"lint:security": "eslint ./src --ext ts --no-eslintrc --config ./.eslintrc.security",
|
|
17
33
|
"test": "jest --coverage --silent",
|
|
18
34
|
"test:watch": "jest --coverage --watch",
|
|
@@ -32,21 +48,23 @@
|
|
|
32
48
|
"publish:cdn": "ccu --trace"
|
|
33
49
|
},
|
|
34
50
|
"devDependencies": {
|
|
35
|
-
"@auth0/component-cdn-uploader": "
|
|
51
|
+
"@auth0/component-cdn-uploader": "^2.2.2",
|
|
36
52
|
"@rollup/plugin-replace": "^4.0.0",
|
|
37
53
|
"@types/cypress": "^1.1.3",
|
|
38
54
|
"@types/jest": "^28.1.7",
|
|
39
55
|
"@typescript-eslint/eslint-plugin-tslint": "^5.33.1",
|
|
40
56
|
"@typescript-eslint/parser": "^5.33.1",
|
|
41
57
|
"browser-tabs-lock": "^1.2.15",
|
|
42
|
-
"browserstack-cypress-cli": "1.
|
|
58
|
+
"browserstack-cypress-cli": "1.32.8",
|
|
43
59
|
"cli-table": "^0.3.6",
|
|
44
60
|
"concurrently": "^7.3.0",
|
|
45
|
-
"
|
|
61
|
+
"dpop": "^2.1.1",
|
|
62
|
+
"cypress": "13.17.0",
|
|
46
63
|
"es-check": "^7.0.1",
|
|
47
64
|
"es-cookie": "~1.3.2",
|
|
48
65
|
"eslint": "^8.22.0",
|
|
49
66
|
"eslint-plugin-security": "^1.5.0",
|
|
67
|
+
"fake-indexeddb": "^6.0.1",
|
|
50
68
|
"gzip-size": "^7.0.0",
|
|
51
69
|
"husky": "^7.0.4",
|
|
52
70
|
"idtoken-verifier": "^2.2.2",
|
|
@@ -98,21 +116,6 @@
|
|
|
98
116
|
"Single Page Application authentication",
|
|
99
117
|
"SPA authentication"
|
|
100
118
|
],
|
|
101
|
-
"ccu": {
|
|
102
|
-
"name": "auth0-spa-js",
|
|
103
|
-
"cdn": "https://cdn.auth0.com",
|
|
104
|
-
"mainBundleFile": "auth0-spa-js.production.js",
|
|
105
|
-
"bucket": "assets.us.auth0.com",
|
|
106
|
-
"localPath": "dist",
|
|
107
|
-
"digest": {
|
|
108
|
-
"hashes": [
|
|
109
|
-
"sha384"
|
|
110
|
-
],
|
|
111
|
-
"extensions": [
|
|
112
|
-
".js"
|
|
113
|
-
]
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
119
|
"husky": {
|
|
117
120
|
"hooks": {
|
|
118
121
|
"pre-commit": "pretty-quick --staged"
|
package/src/Auth0Client.ts
CHANGED
|
@@ -93,6 +93,12 @@ import {
|
|
|
93
93
|
patchOpenUrlWithOnRedirect
|
|
94
94
|
} from './Auth0Client.utils';
|
|
95
95
|
import { CustomTokenExchangeOptions } from './TokenExchange';
|
|
96
|
+
import { Dpop } from './dpop/dpop';
|
|
97
|
+
import {
|
|
98
|
+
Fetcher,
|
|
99
|
+
type FetcherConfig,
|
|
100
|
+
type CustomFetchMinimalOutput
|
|
101
|
+
} from './fetcher';
|
|
96
102
|
|
|
97
103
|
/**
|
|
98
104
|
* @ignore
|
|
@@ -119,6 +125,7 @@ export class Auth0Client {
|
|
|
119
125
|
private readonly tokenIssuer: string;
|
|
120
126
|
private readonly scope: string;
|
|
121
127
|
private readonly cookieStorage: ClientStorage;
|
|
128
|
+
private readonly dpop: Dpop | undefined;
|
|
122
129
|
private readonly sessionCheckExpiryDays: number;
|
|
123
130
|
private readonly orgHintCookieName: string;
|
|
124
131
|
private readonly isAuthenticatedCookieName: string;
|
|
@@ -130,7 +137,6 @@ export class Auth0Client {
|
|
|
130
137
|
private readonly userCache: ICache = new InMemoryCache().enclosedCache;
|
|
131
138
|
|
|
132
139
|
private worker?: Worker;
|
|
133
|
-
|
|
134
140
|
private readonly defaultOptions: Partial<Auth0ClientOptions> = {
|
|
135
141
|
authorizationParams: {
|
|
136
142
|
scope: DEFAULT_SCOPE
|
|
@@ -222,6 +228,10 @@ export class Auth0Client {
|
|
|
222
228
|
this.nowProvider
|
|
223
229
|
);
|
|
224
230
|
|
|
231
|
+
this.dpop = this.options.useDpop
|
|
232
|
+
? new Dpop(this.options.clientId)
|
|
233
|
+
: undefined;
|
|
234
|
+
|
|
225
235
|
this.domainUrl = getDomain(this.options.domain);
|
|
226
236
|
this.tokenIssuer = getTokenIssuer(this.options.issuer, this.domainUrl);
|
|
227
237
|
|
|
@@ -301,6 +311,7 @@ export class Auth0Client {
|
|
|
301
311
|
const code_verifier = createRandomString();
|
|
302
312
|
const code_challengeBuffer = await sha256(code_verifier);
|
|
303
313
|
const code_challenge = bufferToBase64UrlEncoded(code_challengeBuffer);
|
|
314
|
+
const thumbprint = await this.dpop?.calculateThumbprint();
|
|
304
315
|
|
|
305
316
|
const params = getAuthorizeParams(
|
|
306
317
|
this.options,
|
|
@@ -312,7 +323,8 @@ export class Auth0Client {
|
|
|
312
323
|
authorizationParams.redirect_uri ||
|
|
313
324
|
this.options.authorizationParams.redirect_uri ||
|
|
314
325
|
fallbackRedirectUri,
|
|
315
|
-
authorizeOptions?.response_mode
|
|
326
|
+
authorizeOptions?.response_mode,
|
|
327
|
+
thumbprint
|
|
316
328
|
);
|
|
317
329
|
|
|
318
330
|
const url = this._authorizeUrl(params);
|
|
@@ -716,11 +728,17 @@ export class Auth0Client {
|
|
|
716
728
|
? await this._getTokenUsingRefreshToken(getTokenOptions)
|
|
717
729
|
: await this._getTokenFromIFrame(getTokenOptions);
|
|
718
730
|
|
|
719
|
-
const {
|
|
720
|
-
|
|
731
|
+
const {
|
|
732
|
+
id_token,
|
|
733
|
+
token_type,
|
|
734
|
+
access_token,
|
|
735
|
+
oauthTokenScope,
|
|
736
|
+
expires_in
|
|
737
|
+
} = authResult;
|
|
721
738
|
|
|
722
739
|
return {
|
|
723
740
|
id_token,
|
|
741
|
+
token_type,
|
|
724
742
|
access_token,
|
|
725
743
|
...(oauthTokenScope ? { scope: oauthTokenScope } : null),
|
|
726
744
|
expires_in
|
|
@@ -848,6 +866,8 @@ export class Auth0Client {
|
|
|
848
866
|
});
|
|
849
867
|
this.userCache.remove(CACHE_KEY_ID_TOKEN_SUFFIX);
|
|
850
868
|
|
|
869
|
+
await this.dpop?.clear();
|
|
870
|
+
|
|
851
871
|
const url = this._buildLogoutUrl(logoutOptions);
|
|
852
872
|
|
|
853
873
|
if (openUrl) {
|
|
@@ -1080,11 +1100,13 @@ export class Auth0Client {
|
|
|
1080
1100
|
);
|
|
1081
1101
|
|
|
1082
1102
|
if (entry && entry.access_token) {
|
|
1083
|
-
const { access_token, oauthTokenScope, expires_in } =
|
|
1103
|
+
const { token_type, access_token, oauthTokenScope, expires_in } =
|
|
1104
|
+
entry as CacheEntry;
|
|
1084
1105
|
const cache = await this._getIdTokenFromCache();
|
|
1085
1106
|
return (
|
|
1086
1107
|
cache && {
|
|
1087
1108
|
id_token: cache.id_token,
|
|
1109
|
+
token_type: token_type ? token_type : 'Bearer',
|
|
1088
1110
|
access_token,
|
|
1089
1111
|
...(oauthTokenScope ? { scope: oauthTokenScope } : null),
|
|
1090
1112
|
expires_in
|
|
@@ -1120,6 +1142,7 @@ export class Auth0Client {
|
|
|
1120
1142
|
auth0Client: this.options.auth0Client,
|
|
1121
1143
|
useFormData: this.options.useFormData,
|
|
1122
1144
|
timeout: this.httpTimeoutMs,
|
|
1145
|
+
dpop: this.dpop,
|
|
1123
1146
|
...options
|
|
1124
1147
|
},
|
|
1125
1148
|
this.worker
|
|
@@ -1211,6 +1234,90 @@ export class Auth0Client {
|
|
|
1211
1234
|
audience: options.audience || this.options.authorizationParams.audience
|
|
1212
1235
|
});
|
|
1213
1236
|
}
|
|
1237
|
+
|
|
1238
|
+
protected _assertDpop(dpop: Dpop | undefined): asserts dpop is Dpop {
|
|
1239
|
+
if (!dpop) {
|
|
1240
|
+
throw new Error('`useDpop` option must be enabled before using DPoP.');
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Returns the current DPoP nonce used for making requests to Auth0.
|
|
1246
|
+
*
|
|
1247
|
+
* It can return `undefined` because when starting fresh it will not
|
|
1248
|
+
* be populated until after the first response from the server.
|
|
1249
|
+
*
|
|
1250
|
+
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
|
|
1251
|
+
*
|
|
1252
|
+
* @param nonce The nonce value.
|
|
1253
|
+
* @param id The identifier of a nonce: if absent, it will get the nonce
|
|
1254
|
+
* used for requests to Auth0. Otherwise, it will be used to
|
|
1255
|
+
* select a specific non-Auth0 nonce.
|
|
1256
|
+
*/
|
|
1257
|
+
public getDpopNonce(id?: string): Promise<string | undefined> {
|
|
1258
|
+
this._assertDpop(this.dpop);
|
|
1259
|
+
|
|
1260
|
+
return this.dpop.getNonce(id);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Sets the current DPoP nonce used for making requests to Auth0.
|
|
1265
|
+
*
|
|
1266
|
+
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
|
|
1267
|
+
*
|
|
1268
|
+
* @param nonce The nonce value.
|
|
1269
|
+
* @param id The identifier of a nonce: if absent, it will set the nonce
|
|
1270
|
+
* used for requests to Auth0. Otherwise, it will be used to
|
|
1271
|
+
* select a specific non-Auth0 nonce.
|
|
1272
|
+
*/
|
|
1273
|
+
public setDpopNonce(nonce: string, id?: string): Promise<void> {
|
|
1274
|
+
this._assertDpop(this.dpop);
|
|
1275
|
+
|
|
1276
|
+
return this.dpop.setNonce(nonce, id);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Returns a string to be used to demonstrate possession of the private
|
|
1281
|
+
* key used to cryptographically bind access tokens with DPoP.
|
|
1282
|
+
*
|
|
1283
|
+
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
|
|
1284
|
+
*/
|
|
1285
|
+
public generateDpopProof(params: {
|
|
1286
|
+
url: string;
|
|
1287
|
+
method: string;
|
|
1288
|
+
nonce?: string;
|
|
1289
|
+
accessToken: string;
|
|
1290
|
+
}): Promise<string> {
|
|
1291
|
+
this._assertDpop(this.dpop);
|
|
1292
|
+
|
|
1293
|
+
return this.dpop.generateProof(params);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Returns a new `Fetcher` class that will contain a `fetchWithAuth()` method.
|
|
1298
|
+
* This is a drop-in replacement for the Fetch API's `fetch()` method, but will
|
|
1299
|
+
* handle certain authentication logic for you, like building the proper auth
|
|
1300
|
+
* headers or managing DPoP nonces and retries automatically.
|
|
1301
|
+
*
|
|
1302
|
+
* Check the `EXAMPLES.md` file for a deeper look into this method.
|
|
1303
|
+
*/
|
|
1304
|
+
public createFetcher<TOutput extends CustomFetchMinimalOutput = Response>(
|
|
1305
|
+
config: FetcherConfig<TOutput> = {}
|
|
1306
|
+
): Fetcher<TOutput> {
|
|
1307
|
+
if (this.options.useDpop && !config.dpopNonceId) {
|
|
1308
|
+
throw new TypeError(
|
|
1309
|
+
'When `useDpop` is enabled, `dpopNonceId` must be set when calling `createFetcher()`.'
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
return new Fetcher(config, {
|
|
1314
|
+
isDpopEnabled: () => !!this.options.useDpop,
|
|
1315
|
+
getAccessToken: () => this.getTokenSilently(),
|
|
1316
|
+
getDpopNonce: () => this.getDpopNonce(config.dpopNonceId),
|
|
1317
|
+
setDpopNonce: nonce => this.setDpopNonce(nonce),
|
|
1318
|
+
generateDpopProof: params => this.generateDpopProof(params)
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1214
1321
|
}
|
|
1215
1322
|
|
|
1216
1323
|
interface BaseRequestTokenOptions {
|
package/src/Auth0Client.utils.ts
CHANGED
|
@@ -57,7 +57,8 @@ export const getAuthorizeParams = (
|
|
|
57
57
|
nonce: string,
|
|
58
58
|
code_challenge: string,
|
|
59
59
|
redirect_uri: string | undefined,
|
|
60
|
-
response_mode: string | undefined
|
|
60
|
+
response_mode: string | undefined,
|
|
61
|
+
thumbprint: string | undefined
|
|
61
62
|
): AuthorizeOptions => {
|
|
62
63
|
return {
|
|
63
64
|
client_id: clientOptions.clientId,
|
|
@@ -71,7 +72,8 @@ export const getAuthorizeParams = (
|
|
|
71
72
|
redirect_uri:
|
|
72
73
|
redirect_uri || clientOptions.authorizationParams.redirect_uri,
|
|
73
74
|
code_challenge,
|
|
74
|
-
code_challenge_method: 'S256'
|
|
75
|
+
code_challenge_method: 'S256',
|
|
76
|
+
dpop_jkt: thumbprint
|
|
75
77
|
};
|
|
76
78
|
};
|
|
77
79
|
|
package/src/api.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TokenEndpointOptions, TokenEndpointResponse } from './global';
|
|
2
2
|
import { DEFAULT_AUTH0_CLIENT } from './constants';
|
|
3
|
+
import * as dpopUtils from './dpop/utils';
|
|
3
4
|
import { getJSON } from './http';
|
|
4
5
|
import { createQueryParams } from './utils';
|
|
5
6
|
|
|
@@ -11,6 +12,7 @@ export async function oauthToken(
|
|
|
11
12
|
scope,
|
|
12
13
|
auth0Client,
|
|
13
14
|
useFormData,
|
|
15
|
+
dpop,
|
|
14
16
|
...options
|
|
15
17
|
}: TokenEndpointOptions,
|
|
16
18
|
worker?: Worker
|
|
@@ -28,6 +30,8 @@ export async function oauthToken(
|
|
|
28
30
|
? createQueryParams(allParams)
|
|
29
31
|
: JSON.stringify(allParams);
|
|
30
32
|
|
|
33
|
+
const isDpopSupported = dpopUtils.isGrantTypeSupported(options.grant_type);
|
|
34
|
+
|
|
31
35
|
return await getJSON<TokenEndpointResponse>(
|
|
32
36
|
`${baseUrl}/oauth/token`,
|
|
33
37
|
timeout,
|
|
@@ -46,6 +50,7 @@ export async function oauthToken(
|
|
|
46
50
|
}
|
|
47
51
|
},
|
|
48
52
|
worker,
|
|
49
|
-
useFormData
|
|
53
|
+
useFormData,
|
|
54
|
+
isDpopSupported ? dpop : undefined
|
|
50
55
|
);
|
|
51
56
|
}
|