@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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/auth0-spa-js.development.js +600 -14
  3. package/dist/auth0-spa-js.development.js.map +1 -1
  4. package/dist/auth0-spa-js.production.esm.js +1 -1
  5. package/dist/auth0-spa-js.production.esm.js.map +1 -1
  6. package/dist/auth0-spa-js.production.js +1 -1
  7. package/dist/auth0-spa-js.production.js.map +1 -1
  8. package/dist/auth0-spa-js.worker.development.js +10 -2
  9. package/dist/auth0-spa-js.worker.development.js.map +1 -1
  10. package/dist/auth0-spa-js.worker.production.js +1 -1
  11. package/dist/auth0-spa-js.worker.production.js.map +1 -1
  12. package/dist/lib/auth0-spa-js.cjs.js +641 -14
  13. package/dist/lib/auth0-spa-js.cjs.js.map +1 -1
  14. package/dist/typings/Auth0Client.d.ts +50 -0
  15. package/dist/typings/Auth0Client.utils.d.ts +1 -1
  16. package/dist/typings/api.d.ts +1 -1
  17. package/dist/typings/cache/shared.d.ts +1 -0
  18. package/dist/typings/dpop/dpop.d.ts +17 -0
  19. package/dist/typings/dpop/storage.d.ts +27 -0
  20. package/dist/typings/dpop/utils.d.ts +15 -0
  21. package/dist/typings/errors.d.ts +7 -0
  22. package/dist/typings/fetcher.d.ts +48 -0
  23. package/dist/typings/global.d.ts +19 -0
  24. package/dist/typings/http.d.ts +2 -1
  25. package/dist/typings/index.d.ts +2 -1
  26. package/dist/typings/utils.d.ts +6 -0
  27. package/dist/typings/version.d.ts +1 -1
  28. package/package.json +22 -19
  29. package/src/Auth0Client.ts +112 -5
  30. package/src/Auth0Client.utils.ts +4 -2
  31. package/src/api.ts +6 -1
  32. package/src/cache/shared.ts +1 -0
  33. package/src/dpop/dpop.ts +56 -0
  34. package/src/dpop/storage.ts +134 -0
  35. package/src/dpop/utils.ts +66 -0
  36. package/src/errors.ts +11 -0
  37. package/src/fetcher.ts +224 -0
  38. package/src/global.ts +21 -0
  39. package/src/http.ts +70 -5
  40. package/src/index.ts +4 -1
  41. package/src/utils.ts +15 -0
  42. package/src/version.ts +1 -1
  43. 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
  *
@@ -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>;
@@ -41,6 +41,7 @@ export interface IdTokenEntry {
41
41
  }
42
42
  export type CacheEntry = {
43
43
  id_token?: string;
44
+ token_type?: string;
44
45
  access_token: string;
45
46
  expires_in: number;
46
47
  decodedToken?: DecodedToken;
@@ -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 {};
@@ -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 {};
@@ -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 {};
@@ -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>;
@@ -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';
@@ -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.3.0";
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.3.0",
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": "github:auth0/component-cdn-uploader#v2.2.2",
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.28.0",
58
+ "browserstack-cypress-cli": "1.32.8",
43
59
  "cli-table": "^0.3.6",
44
60
  "concurrently": "^7.3.0",
45
- "cypress": "13.6.1",
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"
@@ -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 { id_token, access_token, oauthTokenScope, expires_in } =
720
- authResult;
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 } = entry as CacheEntry;
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 {
@@ -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
  }
@@ -73,6 +73,7 @@ export interface IdTokenEntry {
73
73
 
74
74
  export type CacheEntry = {
75
75
  id_token?: string;
76
+ token_type?: string;
76
77
  access_token: string;
77
78
  expires_in: number;
78
79
  decodedToken?: DecodedToken;