@auth0/auth0-spa-js 1.21.1 → 1.22.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.
@@ -17,6 +17,7 @@ export default class Auth0Client {
17
17
  private readonly isAuthenticatedCookieName;
18
18
  private readonly nowProvider;
19
19
  private readonly httpTimeoutMs;
20
+ private readonly useRefreshTokensFallback;
20
21
  cacheLocation: CacheLocation;
21
22
  private worker;
22
23
  constructor(options: Auth0ClientOptions);
@@ -24,7 +24,7 @@ export declare const CACHE_LOCATION_LOCAL_STORAGE = "localstorage";
24
24
  /**
25
25
  * @ignore
26
26
  */
27
- export declare const MISSING_REFRESH_TOKEN_ERROR_MESSAGE = "The web worker is missing the refresh token";
27
+ export declare const MISSING_REFRESH_TOKEN_ERROR_MESSAGE = "Missing Refresh Token";
28
28
  /**
29
29
  * @ignore
30
30
  */
@@ -48,3 +48,8 @@ export declare class MfaRequiredError extends GenericError {
48
48
  mfa_token: string;
49
49
  constructor(error: string, error_description: string, mfa_token: string);
50
50
  }
51
+ export declare class MissingRefreshTokenError extends GenericError {
52
+ audience: string;
53
+ scope: string;
54
+ constructor(audience: string, scope: string);
55
+ }
@@ -138,6 +138,26 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
138
138
  * **Note**: Use of refresh tokens must be enabled by an administrator on your Auth0 client application.
139
139
  */
140
140
  useRefreshTokens?: boolean;
141
+ /**
142
+ * If true, fallback to the technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` when unable to use refresh tokens.
143
+ * The default setting is `true`.
144
+ *
145
+ * **Note**: There might be situations where doing silent auth with a Web Message response from an iframe is not possible,
146
+ * like when you're serving your application from the file system or a custom protocol (like in a Desktop or Native app).
147
+ * In situations like this you can disable the iframe fallback and handle the failed Refresh Grant and prompt the user to login interactively with `loginWithRedirect` or `loginWithPopup`."
148
+ *
149
+ * E.g. Using the `file:` protocol in an Electron application does not support that legacy technique.
150
+ *
151
+ * let token: string;
152
+ * try {
153
+ * token = await auth0.getTokenSilently();
154
+ * } catch (e) {
155
+ * if (e.error === 'missing_refresh_token' || e.error === 'invalid_grant') {
156
+ * auth0.loginWithRedirect();
157
+ * }
158
+ * }
159
+ */
160
+ useRefreshTokensFallback?: boolean;
141
161
  /**
142
162
  * A maximum number of seconds to wait before declaring background calls to /authorize as failed for timeout
143
163
  * Defaults to 60s.
@@ -13,3 +13,10 @@ export declare const sha256: (s: string) => Promise<any>;
13
13
  export declare const urlDecodeB64: (input: string) => string;
14
14
  export declare const bufferToBase64UrlEncoded: (input: number[] | Uint8Array) => string;
15
15
  export declare const validateCrypto: () => void;
16
+ /**
17
+ * Returns an empty string when value is falsy, or when it's value is included in the exclude argument.
18
+ * @param value The value to check
19
+ * @param exclude An array of values that should result in an empty string.
20
+ * @returns The value, or an empty string when falsy or included in the exclude argument.
21
+ */
22
+ export declare function valueOrEmptyString(value: string, exclude?: string[]): string;
@@ -1,2 +1,2 @@
1
- declare const _default: "1.21.1";
1
+ declare const _default: "1.22.0";
2
2
  export default _default;
package/package.json CHANGED
@@ -3,7 +3,7 @@
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": "1.21.1",
6
+ "version": "1.22.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",
@@ -81,7 +81,7 @@
81
81
  "dependencies": {
82
82
  "abortcontroller-polyfill": "^1.7.3",
83
83
  "browser-tabs-lock": "^1.2.15",
84
- "core-js": "^3.22.0",
84
+ "core-js": "^3.22.4",
85
85
  "es-cookie": "^1.3.2",
86
86
  "fast-text-encoding": "^1.0.3",
87
87
  "promise-polyfill": "^8.2.3",
@@ -27,7 +27,12 @@ import {
27
27
 
28
28
  import TransactionManager from './transaction-manager';
29
29
  import { verify as verifyIdToken } from './jwt';
30
- import { AuthenticationError, GenericError, TimeoutError } from './errors';
30
+ import {
31
+ AuthenticationError,
32
+ GenericError,
33
+ MissingRefreshTokenError,
34
+ TimeoutError
35
+ } from './errors';
31
36
 
32
37
  import {
33
38
  ClientStorage,
@@ -202,6 +207,7 @@ export default class Auth0Client {
202
207
  private readonly isAuthenticatedCookieName: string;
203
208
  private readonly nowProvider: () => number | Promise<number>;
204
209
  private readonly httpTimeoutMs: number;
210
+ private readonly useRefreshTokensFallback: boolean;
205
211
 
206
212
  cacheLocation: CacheLocation;
207
213
  private worker: Worker;
@@ -299,6 +305,9 @@ export default class Auth0Client {
299
305
  }
300
306
 
301
307
  this.customOptions = getCustomInitialOptions(options);
308
+
309
+ this.useRefreshTokensFallback =
310
+ this.options.useRefreshTokensFallback !== false;
302
311
  }
303
312
 
304
313
  private _url(path: string) {
@@ -1183,7 +1192,14 @@ export default class Auth0Client {
1183
1192
  // and you don't have a refresh token in web worker memory
1184
1193
  // fallback to an iframe.
1185
1194
  if ((!cache || !cache.refresh_token) && !this.worker) {
1186
- return await this._getTokenFromIFrame(options);
1195
+ if (this.useRefreshTokensFallback) {
1196
+ return await this._getTokenFromIFrame(options);
1197
+ }
1198
+
1199
+ throw new MissingRefreshTokenError(
1200
+ options.audience || 'default',
1201
+ options.scope
1202
+ );
1187
1203
  }
1188
1204
 
1189
1205
  const redirect_uri =
@@ -1230,11 +1246,12 @@ export default class Auth0Client {
1230
1246
  if (
1231
1247
  // The web worker didn't have a refresh token in memory so
1232
1248
  // fallback to an iframe.
1233
- e.message === MISSING_REFRESH_TOKEN_ERROR_MESSAGE ||
1234
- // A refresh token was found, but is it no longer valid.
1235
- // Fallback to an iframe.
1236
- (e.message &&
1237
- e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1)
1249
+ (e.message.indexOf(MISSING_REFRESH_TOKEN_ERROR_MESSAGE) > -1 ||
1250
+ // A refresh token was found, but is it no longer valid.
1251
+ // Fallback to an iframe.
1252
+ (e.message &&
1253
+ e.message.indexOf(INVALID_REFRESH_TOKEN_ERROR_MESSAGE) > -1)) &&
1254
+ this.useRefreshTokensFallback
1238
1255
  ) {
1239
1256
  return await this._getTokenFromIFrame(options);
1240
1257
  }
package/src/constants.ts CHANGED
@@ -34,8 +34,7 @@ export const CACHE_LOCATION_LOCAL_STORAGE = 'localstorage';
34
34
  /**
35
35
  * @ignore
36
36
  */
37
- export const MISSING_REFRESH_TOKEN_ERROR_MESSAGE =
38
- 'The web worker is missing the refresh token';
37
+ export const MISSING_REFRESH_TOKEN_ERROR_MESSAGE = 'Missing Refresh Token';
39
38
 
40
39
  /**
41
40
  * @ignore
package/src/errors.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  * https://github.com/gotwarlost/istanbul/issues/690
4
4
  */
5
5
 
6
+ import { valueOrEmptyString } from './utils';
7
+
6
8
  /**
7
9
  * Thrown when network requests to the Auth server fail.
8
10
  */
@@ -91,3 +93,16 @@ export class MfaRequiredError extends GenericError {
91
93
  Object.setPrototypeOf(this, MfaRequiredError.prototype);
92
94
  }
93
95
  }
96
+
97
+ export class MissingRefreshTokenError extends GenericError {
98
+ /* istanbul ignore next */
99
+ constructor(public audience: string, public scope: string) {
100
+ super(
101
+ 'missing_refresh_token',
102
+ `Missing Refresh Token (audience: '${valueOrEmptyString(audience, [
103
+ 'default'
104
+ ])}', scope: '${valueOrEmptyString(scope)}')`
105
+ );
106
+ Object.setPrototypeOf(this, MissingRefreshTokenError.prototype);
107
+ }
108
+ }
package/src/global.ts CHANGED
@@ -158,6 +158,27 @@ export interface Auth0ClientOptions extends BaseLoginOptions {
158
158
  */
159
159
  useRefreshTokens?: boolean;
160
160
 
161
+ /**
162
+ * If true, fallback to the technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` when unable to use refresh tokens.
163
+ * The default setting is `true`.
164
+ *
165
+ * **Note**: There might be situations where doing silent auth with a Web Message response from an iframe is not possible,
166
+ * like when you're serving your application from the file system or a custom protocol (like in a Desktop or Native app).
167
+ * In situations like this you can disable the iframe fallback and handle the failed Refresh Grant and prompt the user to login interactively with `loginWithRedirect` or `loginWithPopup`."
168
+ *
169
+ * E.g. Using the `file:` protocol in an Electron application does not support that legacy technique.
170
+ *
171
+ * let token: string;
172
+ * try {
173
+ * token = await auth0.getTokenSilently();
174
+ * } catch (e) {
175
+ * if (e.error === 'missing_refresh_token' || e.error === 'invalid_grant') {
176
+ * auth0.loginWithRedirect();
177
+ * }
178
+ * }
179
+ */
180
+ useRefreshTokensFallback?: boolean;
181
+
161
182
  /**
162
183
  * A maximum number of seconds to wait before declaring background calls to /authorize as failed for timeout
163
184
  * Defaults to 60s.
package/src/utils.ts CHANGED
@@ -241,3 +241,13 @@ export const validateCrypto = () => {
241
241
  `);
242
242
  }
243
243
  };
244
+
245
+ /**
246
+ * Returns an empty string when value is falsy, or when it's value is included in the exclude argument.
247
+ * @param value The value to check
248
+ * @param exclude An array of values that should result in an empty string.
249
+ * @returns The value, or an empty string when falsy or included in the exclude argument.
250
+ */
251
+ export function valueOrEmptyString(value: string, exclude: string[] = []) {
252
+ return value && !exclude.includes(value) ? value : '';
253
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export default '1.21.1';
1
+ export default '1.22.0';
@@ -1,4 +1,4 @@
1
- import { MISSING_REFRESH_TOKEN_ERROR_MESSAGE } from '../constants';
1
+ import { MissingRefreshTokenError } from '../errors';
2
2
  import { WorkerRefreshTokenMessage } from './worker.types';
3
3
 
4
4
  let refreshTokens: Record<string, string> = {};
@@ -50,7 +50,7 @@ const messageHandler = async ({
50
50
  const refreshToken = getRefreshToken(audience, scope);
51
51
 
52
52
  if (!refreshToken) {
53
- throw new Error(MISSING_REFRESH_TOKEN_ERROR_MESSAGE);
53
+ throw new MissingRefreshTokenError(audience, scope);
54
54
  }
55
55
 
56
56
  fetchOptions.body = useFormData