This commit is contained in:
nucther committed 2025-12-01 23:23:05 +07:00
commit d282e2bcae
3481 files changed
+568488

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Auth0, Inc. <support@auth0.com> (http://auth0.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+160
View File
@@ -0,0 +1,160 @@
![Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE.](https://cdn.auth0.com/website/sdks/banners/spa-js-banner.png)
[![npm version](https://img.shields.io/npm/v/@auth0/auth0-spa-js?style=flat-square&logo=npm&logoColor=CB3837)](https://www.npmjs.com/package/@auth0/auth0-spa-js)
[![Codecov](https://img.shields.io/codecov/c/github/auth0/auth0-spa-js)](https://codecov.io/gh/auth0/auth0-spa-js)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/auth0/auth0-spa-js)
![Downloads](https://img.shields.io/npm/dw/@auth0/auth0-spa-js)
[![License](https://img.shields.io/:license-mit-blue.svg?style=flat)](https://opensource.org/licenses/MIT)
![CircleCI](https://img.shields.io/circleci/build/github/auth0/auth0-spa-js)
📚 [Documentation](#documentation) - 🚀 [Getting Started](#getting-started) - 💻 [API Reference](#api-reference) - 💬 [Feedback](#feedback)
## Documentation
- [Quickstart](https://auth0.com/docs/quickstart/spa/vanillajs/interactive) - our interactive guide for quickly adding login, logout and user information to your app using Auth0.
- [Sample app](https://github.com/auth0-samples/auth0-javascript-samples/tree/master/01-Login) - a full-fledged sample app integrated with Auth0.
- [FAQs](https://github.com/auth0/auth0-spa-js/blob/main/FAQ.md) - frequently asked questions about auth0-spa-js SDK.
- [Examples](https://github.com/auth0/auth0-spa-js/blob/main/EXAMPLES.md) - code samples for common scenarios.
- [Docs Site](https://auth0.com/docs) - explore our Docs site and learn more about Auth0.
## Getting Started
### Installation
Using [npm](https://npmjs.org) in your project directory run the following command:
```sh
npm install @auth0/auth0-spa-js
```
From the CDN:
```html
<script src="https://cdn.auth0.com/js/auth0-spa-js/2.9/auth0-spa-js.production.js"></script>
```
### Configure Auth0
Create a **Single Page Application** in the [Auth0 Dashboard](https://manage.auth0.com/#/applications).
> **If you're using an existing application**, verify that you have configured the following settings in your Single Page Application:
>
> - Click on the "Settings" tab of your application's page.
> - Scroll down and click on the "Show Advanced Settings" link.
> - Under "Advanced Settings", click on the "OAuth" tab.
> - Ensure that "JsonWebToken Signature Algorithm" is set to `RS256` and that "OIDC Conformant" is enabled.
Next, configure the following URLs for your application under the "Application URIs" section of the "Settings" page:
- **Allowed Callback URLs**: `http://localhost:3000`
- **Allowed Logout URLs**: `http://localhost:3000`
- **Allowed Web Origins**: `http://localhost:3000`
> These URLs should reflect the origins that your application is running on. **Allowed Callback URLs** may also include a path, depending on where you're handling the callback (see below).
Take note of the **Client ID** and **Domain** values under the "Basic Information" section. You'll need these values in the next step.
### Configure the SDK
Create an `Auth0Client` instance before rendering or initializing your application. You should only have one instance of the client.
```js
import { createAuth0Client } from '@auth0/auth0-spa-js';
//with async/await
const auth0 = await createAuth0Client({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
authorizationParams: {
redirect_uri: '<MY_CALLBACK_URL>'
}
});
//or, you can just instantiate the client on its own
import { Auth0Client } from '@auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: '<AUTH0_DOMAIN>',
clientId: '<AUTH0_CLIENT_ID>',
authorizationParams: {
redirect_uri: '<MY_CALLBACK_URL>'
}
});
//if you do this, you'll need to check the session yourself
try {
await auth0.getTokenSilently();
} catch (error) {
if (error.error !== 'login_required') {
throw error;
}
}
```
### Logging In
You can then use login using the `Auth0Client` instance you created:
```html
<button id="login">Click to Login</button>
```
```js
//redirect to the Universal Login Page
document.getElementById('login').addEventListener('click', async () => {
await auth0.loginWithRedirect();
});
//in your callback route (<MY_CALLBACK_URL>)
window.addEventListener('load', async () => {
const redirectResult = await auth0.handleRedirectCallback();
//logged in. you can get the user profile like this:
const user = await auth0.getUser();
console.log(user);
});
```
For other comprehensive examples, see the [EXAMPLES.md](https://github.com/auth0/auth0-spa-js/blob/main/EXAMPLES.md) document.
## API Reference
Explore API Methods available in auth0-spa-js.
- [Configuration Options](https://auth0.github.io/auth0-spa-js/interfaces/Auth0ClientOptions.html)
- [Auth0Client](https://auth0.github.io/auth0-spa-js/classes/Auth0Client.html)
- [createAuth0Client](https://auth0.github.io/auth0-spa-js/functions/createAuth0Client.html)
## Feedback
### Contributing
We appreciate feedback and contribution to this repo! Before you get started, please see the following:
- [Auth0's general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [Auth0's code of conduct guidelines](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [This repo's contribution guide](https://github.com/auth0/auth0-spa-js/blob/main/CONTRIBUTING.md)
### Raise an issue
To provide feedback or report a bug, please [raise an issue on our issue tracker](https://github.com/auth0/auth0-spa-js/issues).
### Vulnerability Reporting
Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
## What is Auth0?
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.auth0.com/website/sdks/logos/auth0_dark_mode.png" width="150">
<source media="(prefers-color-scheme: light)" srcset="https://cdn.auth0.com/website/sdks/logos/auth0_light_mode.png" width="150">
<img alt="Auth0 Logo" src="https://cdn.auth0.com/website/sdks/logos/auth0_light_mode.png" width="150">
</picture>
</p>
<p align="center">
Auth0 is an easy to implement, adaptable authentication and authorization platform. To learn more checkout <a href="https://auth0.com/why-auth0">Why Auth0?</a>
</p>
<p align="center">
This project is licensed under the MIT license. See the <a href="https://github.com/auth0/auth0-spa-js/blob/main/LICENSE"> LICENSE</a> file for more info.
</p>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
{"version":3,"file":"auth0-spa-js.development.js","sources":["../node_modules/tslib/tslib.es6.js","../node_modules/browser-tabs-lock/processLock.js","../node_modules/browser-tabs-lock/index.js","../src/version.ts","../src/constants.ts","../src/errors.ts","../src/utils.ts","../node_modules/dpop/src/index.ts","../src/dpop/utils.ts","../src/worker/worker.utils.ts","../src/http.ts","../src/api.ts","../src/scope.ts","../src/cache/shared.ts","../src/cache/cache-localstorage.ts","../src/cache/cache-memory.ts","../src/cache/cache-manager.ts","../src/transaction-manager.ts","../src/jwt.ts","../node_modules/es-cookie/src/es-cookie.js","../src/storage.ts","../src/global.ts","../src/promise-utils.ts","../src/cache/key-manifest.ts","../src/Auth0Client.utils.ts","../src/dpop/storage.ts","../src/dpop/dpop.ts","../src/fetcher.ts","../src/MyAccountApiClient.ts","../src/Auth0Client.ts","../src/index.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n contLine truncated
+2
View File
@@ -0,0 +1,2 @@
function e(e,t){var n={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(o=Object.getOwnPropertySymbols(e);i<o.length;i++)t.indexOf(o[i])<0&&Object.prototype.propertyIsEnumerable.call(e,o[i])&&(n[o[i]]=e[o[i]])}return n}"function"==typeof SuppressedError&&SuppressedError;var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function o(e,t){return e(t={exports:{}},t.exports),t.exports}var i=o((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){var e=this;this.locked=new Map,this.addToLocked=function(t,n){var o=e.locked.get(t);void 0===o?void 0===n?e.locked.set(t,[]):e.locked.set(t,[n]):void 0!==n&&(o.unshift(n),e.locked.set(t,o))},this.isLocked=function(t){return e.locked.has(t)},this.lock=function(t){return new Promise((function(n,o){e.isLocked(t)?e.addToLocked(t,n):(e.addToLocked(t),n())}))},this.unlock=function(t){var n=e.locked.get(t);if(void 0!==n&&0!==n.length){var o=n.pop();e.locked.set(t,n),void 0!==o&&setTimeout(o,0)}else e.locked.delete(t)}}return e.getInstance=function(){return void 0===e.instance&&(e.instance=new e),e.instance},e}();t.default=function(){return n.getInstance()}}));n(i);var r=n(o((function(e,n){var o=t&&t.__awaiter||function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function a(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){e.done?i(e.value):new n((function(t){t(e.value)})).then(s,a)}c((o=o.apply(e,t||[])).next())}))},r=t&&t.__generator||function(e,t){var n,o,i,r,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return r={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(r[Symbol.iterator]=function(){return this}),r;function a(r){return function(a){return function(r){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,o&&(i=2&r[0]?o.return:r[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,r[1])).done)return i;switch(o=0,i&&(r=[2&r[0],i.value]),r[0]){case 0:case 1:i=r;break;case 4:return s.label++,{value:r[1],done:!1};case 5:s.label++,o=r[1],r=[0];continue;case 7:r=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==r[0]&&2!==r[0])){s=0;continue}if(3===r[0]&&(!i||r[1]>i[0]&&r[1]<i[3])){s.label=r[1];break}if(6===r[0]&&s.label<i[1]){s.label=i[1],i=r;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(r);break}i[2]&&s.ops.pop(),s.trys.pop();continue}r=t.call(e,s)}catch(e){r=[6,e],o=0}finally{n=i=0}if(5&r[0])throw r[1];return{value:r[0]?r[1]:void 0,done:!0}}([r,a])}}},s=t;Object.defineProperty(n,"__esModule",{value:!0});var a="browser-tabs-lock-key",c={key:function(e){return o(s,void 0,void 0,(function(){return r(this,(function(e){throw new Error("Unsupported")}))}))},getItem:function(e){return o(s,void 0,void 0,(function(){return r(this,(function(e){throw new Error("Unsupported")}))}))},clear:function(){return o(s,void 0,void 0,(function(){return r(this,(function(e){return[2,window.localStorage.clear()]}))}))},removeItem:function(e){return o(s,void 0,void 0,(function(){return r(this,(function(e){throw new Error("Unsupported")}))}))},setItem:function(e,t){return o(s,void 0,void 0,(function(){return r(this,(function(e){throw new Error("Unsupported")}))}))},keySync:function(e){return window.localStorage.key(e)},getItemSync:function(e){return window.localStorage.getItem(e)},clearSync:function(){return window.localStorage.clear()},removeItemSync:function(e){return window.localStorage.removeItem(e)},setItemSync:function(e,t){return window.localStorage.setItem(e,t)}};function u(e){return new Promise((function(t){return setTimeout(t,e)}))}function d(e){for(var t="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",n="",o=0;o<e;o++){n+=t[Math.floor(Math.random()*t.length)]}return n}var h=function(){function e(t){this.acquiredIatSet=new Set,this.storageHandler=void 0,this.id=Date.now().toString()+d(15),this.acquireLock=this.acquireLock.bind(this),this.releaseLock=this.releaseLock.bind(this),this.releaseLock__private__=this.releaseLock__private__.bind(this),this.waitForSomethingToChange=this.waitForSomethingToChange.bind(this),this.refreshLockWhileAcquired=this.refreshLockWhileAcquired.bind(this),this.storageHandler=t,void 0===e.waiters&&(e.waiters=[])}return e.prototype.acquireLock=function(t,n){return void 0===n&&(n=5e3),o(this,void 0,void 0,(function(){var o,i,s,h,l,p,m;return r(this,(function(r){switch(r.label){case 0:o=Date.now()+d(4),i=Date.now()+n,s=a+"-"+t,h=void 0===this.storageHandler?c:this.storageHandler,r.label=1;case 1:return Date.now()<i?[4,u(30)]:[3,8];case 2:return r.sent(),null!==h.getItemSync(s)?[3,5]:(l=this.id+"-"+t+"-"+o,[4,u(Math.floor(25*Math.randomLine truncated
//# sourceMappingURL=auth0-spa-js.production.esm.js.map
@@ -0,0 +1 @@
{"version":3,"file":"auth0-spa-js.production.esm.js","sources":["../node_modules/tslib/tslib.es6.js","../node_modules/browser-tabs-lock/processLock.js","../node_modules/browser-tabs-lock/index.js","../src/constants.ts","../src/version.ts","../src/errors.ts","../src/utils.ts","../node_modules/dpop/src/index.ts","../src/dpop/utils.ts","../src/worker/worker.utils.ts","../src/http.ts","../src/api.ts","../src/scope.ts","../src/cache/shared.ts","../src/cache/cache-localstorage.ts","../src/cache/cache-memory.ts","../src/cache/cache-manager.ts","../src/transaction-manager.ts","../src/jwt.ts","../node_modules/es-cookie/src/es-cookie.js","../src/storage.ts","../src/global.ts","../src/promise-utils.ts","../src/cache/key-manifest.ts","../src/Auth0Client.utils.ts","../src/dpop/storage.ts","../src/dpop/dpop.ts","../src/fetcher.ts","../src/MyAccountApiClient.ts","../src/Auth0Client.ts","../src/index.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n cLine truncated
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).auth0={})}(this,(function(e){"use strict";function t(e,t){var o={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(o[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(n=Object.getOwnPropertySymbols(e);i<n.length;i++)t.indexOf(n[i])<0&&Object.prototype.propertyIsEnumerable.call(e,n[i])&&(o[n[i]]=e[n[i]])}return o}"function"==typeof SuppressedError&&SuppressedError;var o="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function i(e,t){return e(t={exports:{}},t.exports),t.exports}var r=i((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(){var e=this;this.locked=new Map,this.addToLocked=function(t,o){var n=e.locked.get(t);void 0===n?void 0===o?e.locked.set(t,[]):e.locked.set(t,[o]):void 0!==o&&(n.unshift(o),e.locked.set(t,n))},this.isLocked=function(t){return e.locked.has(t)},this.lock=function(t){return new Promise((function(o,n){e.isLocked(t)?e.addToLocked(t,o):(e.addToLocked(t),o())}))},this.unlock=function(t){var o=e.locked.get(t);if(void 0!==o&&0!==o.length){var n=o.pop();e.locked.set(t,o),void 0!==n&&setTimeout(n,0)}else e.locked.delete(t)}}return e.getInstance=function(){return void 0===e.instance&&(e.instance=new e),e.instance},e}();t.default=function(){return o.getInstance()}}));n(r);var s=i((function(e,t){var n=o&&o.__awaiter||function(e,t,o,n){return new(o||(o=Promise))((function(i,r){function s(e){try{c(n.next(e))}catch(e){r(e)}}function a(e){try{c(n.throw(e))}catch(e){r(e)}}function c(e){e.done?i(e.value):new o((function(t){t(e.value)})).then(s,a)}c((n=n.apply(e,t||[])).next())}))},i=o&&o.__generator||function(e,t){var o,n,i,r,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return r={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(r[Symbol.iterator]=function(){return this}),r;function a(r){return function(a){return function(r){if(o)throw new TypeError("Generator is already executing.");for(;s;)try{if(o=1,n&&(i=2&r[0]?n.return:r[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,r[1])).done)return i;switch(n=0,i&&(r=[2&r[0],i.value]),r[0]){case 0:case 1:i=r;break;case 4:return s.label++,{value:r[1],done:!1};case 5:s.label++,n=r[1],r=[0];continue;case 7:r=s.ops.pop(),s.trys.pop();continue;default:if(!(i=s.trys,(i=i.length>0&&i[i.length-1])||6!==r[0]&&2!==r[0])){s=0;continue}if(3===r[0]&&(!i||r[1]>i[0]&&r[1]<i[3])){s.label=r[1];break}if(6===r[0]&&s.label<i[1]){s.label=i[1],i=r;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(r);break}i[2]&&s.ops.pop(),s.trys.pop();continue}r=t.call(e,s)}catch(e){r=[6,e],n=0}finally{o=i=0}if(5&r[0])throw r[1];return{value:r[0]?r[1]:void 0,done:!0}}([r,a])}}},s=o;Object.defineProperty(t,"__esModule",{value:!0});var a="browser-tabs-lock-key",c={key:function(e){return n(s,void 0,void 0,(function(){return i(this,(function(e){throw new Error("Unsupported")}))}))},getItem:function(e){return n(s,void 0,void 0,(function(){return i(this,(function(e){throw new Error("Unsupported")}))}))},clear:function(){return n(s,void 0,void 0,(function(){return i(this,(function(e){return[2,window.localStorage.clear()]}))}))},removeItem:function(e){return n(s,void 0,void 0,(function(){return i(this,(function(e){throw new Error("Unsupported")}))}))},setItem:function(e,t){return n(s,void 0,void 0,(function(){return i(this,(function(e){throw new Error("Unsupported")}))}))},keySync:function(e){return window.localStorage.key(e)},getItemSync:function(e){return window.localStorage.getItem(e)},clearSync:function(){return window.localStorage.clear()},removeItemSync:function(e){return window.localStorage.removeItem(e)},setItemSync:function(e,t){return window.localStorage.setItem(e,t)}};function u(e){return new Promise((function(t){return setTimeout(t,e)}))}function d(e){for(var t="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",o="",n=0;n<e;n++){o+=t[Math.floor(Math.random()*t.length)]}return o}var h=function(){function e(t){this.acquiredIatSet=new Set,this.storageHandler=void 0,this.id=Date.now().toString()+d(15),this.acquireLock=this.acquireLock.bind(this),this.releaseLock=this.releaseLock.bind(this),this.releaseLock__private__=this.releaseLock__private__.bind(this),this.waitForSomethingToChange=this.waitForSomethingToChange.bind(this),this.refreshLockWhileAcquired=this.refreshLockWhileAcquired.bind(this),this.storageHandler=t,void 0===e.waiters&&(e.waiters=[])}return e.prototype.acquireLock=function(t,o){return void 0===o&&(o=5e3),n(this,void 0,void 0,(function(){var n,r,s,h,l,p,m;return i(this,(function(i){switch(i.label){case 0:n=Date.now()+d(4),r=Line truncated
//# sourceMappingURL=auth0-spa-js.production.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"auth0-spa-js.production.js","sources":["../node_modules/tslib/tslib.es6.js","../node_modules/browser-tabs-lock/processLock.js","../node_modules/browser-tabs-lock/index.js","../src/constants.ts","../src/version.ts","../src/errors.ts","../src/utils.ts","../node_modules/dpop/src/index.ts","../src/dpop/utils.ts","../src/worker/worker.utils.ts","../src/http.ts","../src/api.ts","../src/scope.ts","../src/cache/shared.ts","../src/cache/cache-localstorage.ts","../src/cache/cache-memory.ts","../src/cache/cache-manager.ts","../src/transaction-manager.ts","../src/jwt.ts","../node_modules/es-cookie/src/es-cookie.js","../src/storage.ts","../src/global.ts","../src/promise-utils.ts","../src/cache/key-manifest.ts","../src/Auth0Client.utils.ts","../src/dpop/storage.ts","../src/dpop/dpop.ts","../src/fetcher.ts","../src/MyAccountApiClient.ts","../src/Auth0Client.ts","../src/index.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n conteLine truncated
@@ -0,0 +1,164 @@
(function(factory) {
typeof define === "function" && define.amd ? define(factory) : factory();
})((function() {
"use strict";
class GenericError extends Error {
constructor(error, error_description) {
super(error_description);
this.error = error;
this.error_description = error_description;
Object.setPrototypeOf(this, GenericError.prototype);
}
static fromPayload({error: error, error_description: error_description}) {
return new GenericError(error, error_description);
}
}
class MissingRefreshTokenError extends GenericError {
constructor(audience, scope) {
super("missing_refresh_token", `Missing Refresh Token (audience: '${valueOrEmptyString(audience, [ "default" ])}', scope: '${valueOrEmptyString(scope)}')`);
this.audience = audience;
this.scope = scope;
Object.setPrototypeOf(this, MissingRefreshTokenError.prototype);
}
}
function valueOrEmptyString(value, exclude = []) {
return value && !exclude.includes(value) ? value : "";
}
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
}
typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const stripUndefined = params => Object.keys(params).filter((k => typeof params[k] !== "undefined")).reduce(((acc, key) => Object.assign(Object.assign({}, acc), {
[key]: params[key]
})), {});
const createQueryParams = _a => {
var {clientId: client_id} = _a, params = __rest(_a, [ "clientId" ]);
return new URLSearchParams(stripUndefined(Object.assign({
client_id: client_id
}, params))).toString();
};
const fromEntries = iterable => [ ...iterable ].reduce(((obj, [key, val]) => {
obj[key] = val;
return obj;
}), {});
let refreshTokens = {};
const cacheKey = (audience, scope) => `${audience}|${scope}`;
const cacheKeyContainsAudience = (audience, cacheKey) => cacheKey.startsWith(`${audience}|`);
const getRefreshToken = (audience, scope) => refreshTokens[cacheKey(audience, scope)];
const setRefreshToken = (refreshToken, audience, scope) => refreshTokens[cacheKey(audience, scope)] = refreshToken;
const deleteRefreshToken = (audience, scope) => delete refreshTokens[cacheKey(audience, scope)];
const wait = time => new Promise((resolve => setTimeout(resolve, time)));
const formDataToObject = formData => {
const queryParams = new URLSearchParams(formData);
const parsedQuery = {};
queryParams.forEach(((val, key) => {
parsedQuery[key] = val;
}));
return parsedQuery;
};
const updateRefreshTokens = (oldRefreshToken, newRefreshToken) => {
Object.entries(refreshTokens).forEach((([key, token]) => {
if (token === oldRefreshToken) {
refreshTokens[key] = newRefreshToken;
}
}));
};
const checkDownscoping = (scope, audience) => {
const findCoincidence = Object.keys(refreshTokens).find((key => {
if (key !== "latest_refresh_token") {
const isSameAudience = cacheKeyContainsAudience(audience, key);
const scopesKey = key.split("|")[1].split(" ");
const requestedScopes = scope.split(" ");
const scopesAreIncluded = requestedScopes.every((key => scopesKey.includes(key)));
return isSameAudience && scopesAreIncluded;
}
}));
return findCoincidence ? true : false;
};
const messageHandler = async ({data: {timeout: timeout, auth: auth, fetchUrl: fetchUrl, fetchOptions: fetchOptions, useFormData: useFormData, useMrrt: useMrrt}, ports: [port]}) => {
let headers = {};
let json;
let refreshToken;
const {audience: audience, scope: scope} = auth || {};
try {
const body = useFormData ? formDataToObject(fetchOptions.body) : JSON.parse(fetchOptions.body);
if (!body.refresh_token && body.grant_type === "refresh_token") {
refreshToken = getRefreshToken(audience, scope);
if (!refreshToken && useMrrt) {
const latestRefreshToken = refreshTokens["latest_refresh_token"];
const isDownscoping = checkDownscoping(scope, audience);
if (latestRefreshToken && !isDownscoping) {
refreshToken = latestRefreshToken;
}
}
if (!refreshToken) {
throw new MissingRefreshTokenError(audience, scope);
}
fetchOptions.body = useFormData ? createQueryParams(Object.assign(Object.assign({}, body), {
refresh_token: refreshToken
})) : JSON.stringify(Object.assign(Object.assign({}, body), {
refresh_token: refreshToken
}));
}
let abortController;
if (typeof AbortController === "function") {
abortController = new AbortController;
fetchOptions.signal = abortController.signal;
}
let response;
try {
response = await Promise.race([ wait(timeout), fetch(fetchUrl, Object.assign({}, fetchOptions)) ]);
} catch (error) {
port.postMessage({
error: error.message
});
return;
}
if (!response) {
if (abortController) abortController.abort();
port.postMessage({
error: "Timeout when executing 'fetch'"
});
return;
}
headers = fromEntries(response.headers);
json = await response.json();
if (json.refresh_token) {
if (useMrrt) {
refreshTokens["latest_refresh_token"] = json.refresh_token;
updateRefreshTokens(refreshToken, json.refresh_token);
}
setRefreshToken(json.refresh_token, audience, scope);
delete json.refresh_token;
} else {
deleteRefreshToken(audience, scope);
}
port.postMessage({
ok: response.ok,
json: json,
headers: headers
});
} catch (error) {
port.postMessage({
ok: false,
json: {
error: error.error,
error_description: error.message
},
headers: headers
});
}
};
{
addEventListener("message", messageHandler);
}
}));
//# sourceMappingURL=auth0-spa-js.worker.development.js.map
@@ -0,0 +1 @@
{"version":3,"file":"auth0-spa-js.worker.development.js","sources":["../src/errors.ts","../node_modules/tslib/tslib.es6.js","../src/utils.ts","../src/worker/token.worker.ts"],"sourcesContent":["/**\n * Thrown when network requests to the Auth server fail.\n */\nexport class GenericError extends Error {\n constructor(public error: string, public error_description: string) {\n super(error_description);\n Object.setPrototypeOf(this, GenericError.prototype);\n }\n\n static fromPayload({\n error,\n error_description\n }: {\n error: string;\n error_description: string;\n }) {\n return new GenericError(error, error_description);\n }\n}\n\n/**\n * Thrown when handling the redirect callback fails, will be one of Auth0's\n * Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses\n */\nexport class AuthenticationError extends GenericError {\n constructor(\n error: string,\n error_description: string,\n public state: string,\n public appState: any = null\n ) {\n super(error, error_description);\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n}\n\n/**\n * Thrown when handling the redirect callback for the connect flow fails, will be one of Auth0's\n * Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses\n */\nexport class ConnectError extends GenericError {\n constructor(\n error: string,\n error_description: string,\n public connection: string,\n public state: string,\n public appState: any = null\n ) {\n super(error, error_description);\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ConnectError.prototype);\n }\n}\n\n/**\n * Thrown when silent auth times out (usually due to a configuration issue) or\n * when network requests to the Auth server timeout.\n */\nexport class TimeoutError extends GenericError {\n constructor() {\n super('timeout', 'Timeout');\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n\n/**\n * Error thrown when the login popup times out (if the user does not complete auth)\n */\nexport class PopupTimeoutError extends TimeoutError {\n constructor(public popup: Window) {\n super();\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, PopupTimeoutError.prototype);\n }\n}\n\nexport class PopupCancelledError extends GenericError {\n constructor(public popup: Window) {\n super('cancelled', 'Popup closed');\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, PopupCancelledError.prototype);\n }\n}\n\n/**\n * Error thrown when the token exchange results in a `mfa_required` error\n */\nexport class MfaRequiredError extends GenericError {\n constructor(\n error: string,\n error_description: string,\n public mfa_token: string\n ) {\n super(error, error_description);\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, MfaRequiredError.prototype);\n }\n}\n\n/**\n * Error thrown when there is no refresh token to use\n */\nexport class MissingRefreshTokenError extends GenericError {\n constructor(public audience: string, public scope: string) {\n super(\n 'missing_refresh_token',\n `Missing Refresh Token (audience: '${valueOrEmptyString(audience, [\n 'default'\n ])}', scope: '${valueOrEmptyString(scope)}')`\n );\n Object.setPrototypeOf(this, MissingRefreshTokenError.prototype);\n }\n}\n\n/**\n * Error thrown when there are missing scopes after refreshing a token\n */\nexport class MissingScopesError extends GenericError {\n constructor(public audience: string, public scope: string) {\n super(\n 'missing_scopes',\n `Missing requested scopes after refresh (audience: '${valueOrEmptyString(audience, [\n 'default'\n ])}', missing scope: '${valueOrEmptyString(scope)}')`\n );\n Object.setPrototypeOf(this, MissingScopesError.prototype);\n }\n}\n\n/**\n * Error thrown when the wrong DPoP nonce is used and a potential subsequent retry wasn't able to fix it.\n */\nexport class UseDpopNonceError extends GenericError {\n constructor(public newDpopNonce: string | undefined) {\n super('usLine truncated
@@ -0,0 +1,2 @@
!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";class e extends Error{constructor(t,r){super(r),this.error=t,this.error_description=r,Object.setPrototypeOf(this,e.prototype)}static fromPayload({error:t,error_description:r}){return new e(t,r)}}class t extends e{constructor(e,s){super("missing_refresh_token",`Missing Refresh Token (audience: '${r(e,["default"])}', scope: '${r(s)}')`),this.audience=e,this.scope=s,Object.setPrototypeOf(this,t.prototype)}}function r(e,t=[]){return e&&!t.includes(e)?e:""}"function"==typeof SuppressedError&&SuppressedError;const s=e=>{var{clientId:t}=e,r=function(e,t){var r={};for(var s in e)Object.prototype.hasOwnProperty.call(e,s)&&t.indexOf(s)<0&&(r[s]=e[s]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(s=Object.getOwnPropertySymbols(e);o<s.length;o++)t.indexOf(s[o])<0&&Object.prototype.propertyIsEnumerable.call(e,s[o])&&(r[s[o]]=e[s[o]])}return r}(e,["clientId"]);return new URLSearchParams((e=>Object.keys(e).filter((t=>void 0!==e[t])).reduce(((t,r)=>Object.assign(Object.assign({},t),{[r]:e[r]})),{}))(Object.assign({client_id:t},r))).toString()};let o={};const n=(e,t)=>`${e}|${t}`;addEventListener("message",(async({data:{timeout:e,auth:r,fetchUrl:i,fetchOptions:c,useFormData:a,useMrrt:f},ports:[p]})=>{let u,d,h={};const{audience:l,scope:y}=r||{};try{const r=a?(e=>{const t=new URLSearchParams(e),r={};return t.forEach(((e,t)=>{r[t]=e})),r})(c.body):JSON.parse(c.body);if(!r.refresh_token&&"refresh_token"===r.grant_type){if(d=((e,t)=>o[n(e,t)])(l,y),!d&&f){const e=o.latest_refresh_token,t=((e,t)=>{const r=Object.keys(o).find((r=>{if("latest_refresh_token"!==r){const s=((e,t)=>t.startsWith(`${e}|`))(t,r),o=r.split("|")[1].split(" "),n=e.split(" ").every((e=>o.includes(e)));return s&&n}}));return!!r})(y,l);e&&!t&&(d=e)}if(!d)throw new t(l,y);c.body=a?s(Object.assign(Object.assign({},r),{refresh_token:d})):JSON.stringify(Object.assign(Object.assign({},r),{refresh_token:d}))}let j,k;"function"==typeof AbortController&&(j=new AbortController,c.signal=j.signal);try{k=await Promise.race([(_=e,new Promise((e=>setTimeout(e,_)))),fetch(i,Object.assign({},c))])}catch(e){return void p.postMessage({error:e.message})}if(!k)return j&&j.abort(),void p.postMessage({error:"Timeout when executing 'fetch'"});g=k.headers,h=[...g].reduce(((e,[t,r])=>(e[t]=r,e)),{}),u=await k.json(),u.refresh_token?(f&&(o.latest_refresh_token=u.refresh_token,O=d,b=u.refresh_token,Object.entries(o).forEach((([e,t])=>{t===O&&(o[e]=b)}))),((e,t,r)=>{o[n(t,r)]=e})(u.refresh_token,l,y),delete u.refresh_token):((e,t)=>{delete o[n(e,t)]})(l,y),p.postMessage({ok:k.ok,json:u,headers:h})}catch(e){p.postMessage({ok:!1,json:{error:e.error,error_description:e.message},headers:h})}var O,b,g,_}))}));
//# sourceMappingURL=auth0-spa-js.worker.production.js.map
@@ -0,0 +1 @@
{"version":3,"file":"auth0-spa-js.worker.production.js","sources":["../src/errors.ts","../node_modules/tslib/tslib.es6.js","../src/utils.ts","../src/worker/token.worker.ts"],"sourcesContent":["/**\n * Thrown when network requests to the Auth server fail.\n */\nexport class GenericError extends Error {\n constructor(public error: string, public error_description: string) {\n super(error_description);\n Object.setPrototypeOf(this, GenericError.prototype);\n }\n\n static fromPayload({\n error,\n error_description\n }: {\n error: string;\n error_description: string;\n }) {\n return new GenericError(error, error_description);\n }\n}\n\n/**\n * Thrown when handling the redirect callback fails, will be one of Auth0's\n * Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses\n */\nexport class AuthenticationError extends GenericError {\n constructor(\n error: string,\n error_description: string,\n public state: string,\n public appState: any = null\n ) {\n super(error, error_description);\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, AuthenticationError.prototype);\n }\n}\n\n/**\n * Thrown when handling the redirect callback for the connect flow fails, will be one of Auth0's\n * Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses\n */\nexport class ConnectError extends GenericError {\n constructor(\n error: string,\n error_description: string,\n public connection: string,\n public state: string,\n public appState: any = null\n ) {\n super(error, error_description);\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, ConnectError.prototype);\n }\n}\n\n/**\n * Thrown when silent auth times out (usually due to a configuration issue) or\n * when network requests to the Auth server timeout.\n */\nexport class TimeoutError extends GenericError {\n constructor() {\n super('timeout', 'Timeout');\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, TimeoutError.prototype);\n }\n}\n\n/**\n * Error thrown when the login popup times out (if the user does not complete auth)\n */\nexport class PopupTimeoutError extends TimeoutError {\n constructor(public popup: Window) {\n super();\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, PopupTimeoutError.prototype);\n }\n}\n\nexport class PopupCancelledError extends GenericError {\n constructor(public popup: Window) {\n super('cancelled', 'Popup closed');\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, PopupCancelledError.prototype);\n }\n}\n\n/**\n * Error thrown when the token exchange results in a `mfa_required` error\n */\nexport class MfaRequiredError extends GenericError {\n constructor(\n error: string,\n error_description: string,\n public mfa_token: string\n ) {\n super(error, error_description);\n //https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work\n Object.setPrototypeOf(this, MfaRequiredError.prototype);\n }\n}\n\n/**\n * Error thrown when there is no refresh token to use\n */\nexport class MissingRefreshTokenError extends GenericError {\n constructor(public audience: string, public scope: string) {\n super(\n 'missing_refresh_token',\n `Missing Refresh Token (audience: '${valueOrEmptyString(audience, [\n 'default'\n ])}', scope: '${valueOrEmptyString(scope)}')`\n );\n Object.setPrototypeOf(this, MissingRefreshTokenError.prototype);\n }\n}\n\n/**\n * Error thrown when there are missing scopes after refreshing a token\n */\nexport class MissingScopesError extends GenericError {\n constructor(public audience: string, public scope: string) {\n super(\n 'missing_scopes',\n `Missing requested scopes after refresh (audience: '${valueOrEmptyString(audience, [\n 'default'\n ])}', missing scope: '${valueOrEmptyString(scope)}')`\n );\n Object.setPrototypeOf(this, MissingScopesError.prototype);\n }\n}\n\n/**\n * Error thrown when the wrong DPoP nonce is used and a potential subsequent retry wasn't able to fix it.\n */\nexport class UseDpopNonceError extends GenericError {\n constructor(public newDpopNonce: string | undefined) {\n super('useLine truncated
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"auth0-spa-js.cjs.js","sources":["../../node_modules/tslib/tslib.es6.js","../../node_modules/browser-tabs-lock/processLock.js","../../node_modules/browser-tabs-lock/index.js","../../src/version.ts","../../src/constants.ts","../../src/errors.ts","../../src/utils.ts","../../node_modules/dpop/src/index.ts","../../src/dpop/utils.ts","../../src/worker/worker.utils.ts","../../src/http.ts","../../src/api.ts","../../src/scope.ts","../../src/cache/shared.ts","../../src/cache/cache-localstorage.ts","../../src/cache/cache-memory.ts","../../src/cache/cache-manager.ts","../../src/transaction-manager.ts","../../src/jwt.ts","../../node_modules/es-cookie/src/es-cookie.js","../../src/storage.ts","../../src/global.ts","../../src/promise-utils.ts","../../src/cache/key-manifest.ts","../../src/Auth0Client.utils.ts","../../src/dpop/storage.ts","../../src/dpop/dpop.ts","../../src/fetcher.ts","../../src/MyAccountApiClient.ts","../../src/Auth0Client.ts","../../src/index.ts"],"sourcesContent":["/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n forLine truncated
+323
View File
@@ -0,0 +1,323 @@
import { Auth0ClientOptions, RedirectLoginOptions, PopupLoginOptions, PopupConfigOptions, RedirectLoginResult, GetTokenSilentlyOptions, GetTokenWithPopupOptions, LogoutOptions, User, IdToken, GetTokenSilentlyVerboseResponse, TokenEndpointResponse, ConnectAccountRedirectResult, RedirectConnectAccountOptions } from './global';
import { CustomTokenExchangeOptions } from './TokenExchange';
import { Dpop } from './dpop/dpop';
import { Fetcher, type FetcherConfig, type CustomFetchMinimalOutput } from './fetcher';
/**
* Auth0 SDK for Single Page Applications using [Authorization Code Grant Flow with PKCE](https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce).
*/
export declare class Auth0Client {
private readonly transactionManager;
private readonly cacheManager;
private readonly domainUrl;
private readonly tokenIssuer;
private readonly scope;
private readonly cookieStorage;
private readonly dpop;
private readonly sessionCheckExpiryDays;
private readonly orgHintCookieName;
private readonly isAuthenticatedCookieName;
private readonly nowProvider;
private readonly httpTimeoutMs;
private readonly options;
private readonly userCache;
private readonly myAccountApi;
private worker?;
private readonly activeLockKeys;
private readonly defaultOptions;
constructor(options: Auth0ClientOptions);
private _url;
private _authorizeUrl;
private _verifyIdToken;
private _processOrgHint;
private _prepareAuthorizeUrl;
/**
* ```js
* try {
* await auth0.loginWithPopup(options);
* } catch(e) {
* if (e instanceof PopupCancelledError) {
* // Popup was closed before login completed
* }
* }
* ```
*
* Opens a popup with the `/authorize` URL using the parameters
* provided as arguments. Random and secure `state` and `nonce`
* parameters will be auto-generated. If the response is successful,
* results will be valid according to their expiration times.
*
* IMPORTANT: This method has to be called from an event handler
* that was started by the user like a button click, for example,
* otherwise the popup will be blocked in most browsers.
*
* @param options
* @param config
*/
loginWithPopup(options?: PopupLoginOptions, config?: PopupConfigOptions): Promise<void>;
/**
* ```js
* const user = await auth0.getUser();
* ```
*
* Returns the user information if available (decoded
* from the `id_token`).
*
* @typeparam TUser The type to return, has to extend {@link User}.
*/
getUser<TUser extends User>(): Promise<TUser | undefined>;
/**
* ```js
* const claims = await auth0.getIdTokenClaims();
* ```
*
* Returns all claims from the id_token if available.
*/
getIdTokenClaims(): Promise<IdToken | undefined>;
/**
* ```js
* await auth0.loginWithRedirect(options);
* ```
*
* Performs a redirect to `/authorize` using the parameters
* provided as arguments. Random and secure `state` and `nonce`
* parameters will be auto-generated.
*
* @param options
*/
loginWithRedirect<TAppState = any>(options?: RedirectLoginOptions<TAppState>): Promise<void>;
/**
* After the browser redirects back to the callback page,
* call `handleRedirectCallback` to handle success and error
* responses from Auth0. If the response is successful, results
* will be valid according to their expiration times.
*/
handleRedirectCallback<TAppState = any>(url?: string): Promise<RedirectLoginResult<TAppState> | ConnectAccountRedirectResult<TAppState>>;
/**
* Handles the redirect callback from the login flow.
*
* @template AppState - The application state persisted from the /authorize redirect.
* @param {string} authenticationResult - The parsed authentication result from the URL.
* @param {string} transaction - The login transaction.
*
* @returns {RedirectLoginResult} Resolves with the persisted app state.
* @throws {GenericError | Error} If the transaction is missing, invalid, or the code exchange fails.
*/
private _handleLoginRedirectCallback;
/**
* Handles the redirect callback from the connect account flow.
* This works the same as the redirect from the login flow expect it verifies the `connect_code`
* with the My Account API rather than the `code` with the Authorization Server.
*
* @template AppState - The application state persisted from the connect redirect.
* @param {string} connectResult - The parsed connect accounts result from the URL.
* @param {string} transaction - The login transaction.
* @returns {Promise<ConnectAccountRedirectResult>} The result of the My Account API, including any persisted app state.
* @throws {GenericError | MyAccountApiError} If the transaction is missing, invalid, or an error is returned from the My Account API.
*/
private _handleConnectAccountRedirectCallback;
/**
* ```js
* await auth0.checkSession();
* ```
*
* Check if the user is logged in using `getTokenSilently`. The difference
* with `getTokenSilently` is that this doesn't return a token, but it will
* pre-fill the token cache.
*
* This method also heeds the `auth0.{clientId}.is.authenticated` cookie, as an optimization
* to prevent calling Auth0 unnecessarily. If the cookie is not present because
* there was no previous login (or it has expired) then tokens will not be refreshed.
*
* It should be used for silently logging in the user when you instantiate the
* `Auth0Client` constructor. You should not need this if you are using the
* `createAuth0Client` factory.
*
* **Note:** the cookie **may not** be present if running an app using a private tab, as some
* browsers clear JS cookie data and local storage when the tab or page is closed, or on page reload. This effectively
* means that `checkSession` could silently return without authenticating the user on page refresh when
* using a private tab, despite having previously logged in. As a workaround, use `getTokenSilently` instead
* and handle the possible `login_required` error [as shown in the readme](https://github.com/auth0/auth0-spa-js#creating-the-client).
*
* @param options
*/
checkSession(options?: GetTokenSilentlyOptions): Promise<void>;
/**
* Fetches a new access token and returns the response from the /oauth/token endpoint, omitting the refresh token.
*
* @param options
*/
getTokenSilently(options: GetTokenSilentlyOptions & {
detailedResponse: true;
}): Promise<GetTokenSilentlyVerboseResponse>;
/**
* Fetches a new access token and returns it.
*
* @param options
*/
getTokenSilently(options?: GetTokenSilentlyOptions): Promise<string>;
private _getTokenSilently;
/**
* ```js
* const token = await auth0.getTokenWithPopup(options);
* ```
* Opens a popup with the `/authorize` URL using the parameters
* provided as arguments. Random and secure `state` and `nonce`
* parameters will be auto-generated. If the response is successful,
* results will be valid according to their expiration times.
*
* @param options
* @param config
*/
getTokenWithPopup(options?: GetTokenWithPopupOptions, config?: PopupConfigOptions): Promise<string | undefined>;
/**
* ```js
* const isAuthenticated = await auth0.isAuthenticated();
* ```
*
* Returns `true` if there's valid information stored,
* otherwise returns `false`.
*
*/
isAuthenticated(): Promise<boolean>;
/**
* ```js
* await auth0.buildLogoutUrl(options);
* ```
*
* Builds a URL to the logout endpoint using the parameters provided as arguments.
* @param options
*/
private _buildLogoutUrl;
/**
* ```js
* await auth0.logout(options);
* ```
*
* Clears the application session and performs a redirect to `/v2/logout`, using
* the parameters provided as arguments, to clear the Auth0 session.
*
* If the `federated` option is specified it also clears the Identity Provider session.
* [Read more about how Logout works at Auth0](https://auth0.com/docs/logout).
*
* @param options
*/
logout(options?: LogoutOptions): Promise<void>;
private _getTokenFromIFrame;
private _getTokenUsingRefreshToken;
private _saveEntryInCache;
private _getIdTokenFromCache;
private _getEntryFromCache;
/**
* Releases any locks acquired by the current page that are not released yet
*
* Get's called on the `pagehide` event.
* https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
*/
private _releaseLockOnPageHide;
private _requestToken;
/**
* Exchanges an external subject token for an Auth0 token via a token exchange request.
*
* @param {CustomTokenExchangeOptions} options - The options required to perform the token exchange.
*
* @returns {Promise<TokenEndpointResponse>} A promise that resolves to the token endpoint response,
* which contains the issued Auth0 tokens.
*
* This method implements the token exchange grant as specified in RFC 8693 by first validating
* the provided subject token type and then constructing a token request to the /oauth/token endpoint.
* The request includes the following parameters:
*
* - `grant_type`: Hard-coded to "urn:ietf:params:oauth:grant-type:token-exchange".
* - `subject_token`: The external token provided via the options.
* - `subject_token_type`: The type of the external token (validated by this function).
* - `scope`: A unique set of scopes, generated by merging the scopes supplied in the options
* with the SDK’s default scopes.
* - `audience`: The target audience from the options, with fallback to the SDK's authorization configuration.
*
* **Example Usage:**
*
* ```
* // Define the token exchange options
* const options: CustomTokenExchangeOptions = {
* subject_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp...',
* subject_token_type: 'urn:acme:legacy-system-token',
* scope: "openid profile"
* };
*
* // Exchange the external token for Auth0 tokens
* try {
* const tokenResponse = await instance.exchangeToken(options);
* // Use tokenResponse.access_token, tokenResponse.id_token, etc.
* } catch (error) {
* // Handle token exchange error
* }
* ```
*/
exchangeToken(options: CustomTokenExchangeOptions): Promise<TokenEndpointResponse>;
protected _assertDpop(dpop: Dpop | undefined): asserts dpop is Dpop;
/**
* Returns the current DPoP nonce used for making requests to Auth0.
*
* It can return `undefined` because when starting fresh it will not
* be populated until after the first response from the server.
*
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
*
* @param nonce The nonce value.
* @param id The identifier of a nonce: if absent, it will get the nonce
* used for requests to Auth0. Otherwise, it will be used to
* select a specific non-Auth0 nonce.
*/
getDpopNonce(id?: string): Promise<string | undefined>;
/**
* Sets the current DPoP nonce used for making requests to Auth0.
*
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
*
* @param nonce The nonce value.
* @param id The identifier of a nonce: if absent, it will set the nonce
* used for requests to Auth0. Otherwise, it will be used to
* select a specific non-Auth0 nonce.
*/
setDpopNonce(nonce: string, id?: string): Promise<void>;
/**
* Returns a string to be used to demonstrate possession of the private
* key used to cryptographically bind access tokens with DPoP.
*
* It requires enabling the {@link Auth0ClientOptions.useDpop} option.
*/
generateDpopProof(params: {
url: string;
method: string;
nonce?: string;
accessToken: string;
}): Promise<string>;
/**
* Returns a new `Fetcher` class that will contain a `fetchWithAuth()` method.
* This is a drop-in replacement for the Fetch API's `fetch()` method, but will
* handle certain authentication logic for you, like building the proper auth
* headers or managing DPoP nonces and retries automatically.
*
* Check the `EXAMPLES.md` file for a deeper look into this method.
*/
createFetcher<TOutput extends CustomFetchMinimalOutput = Response>(config?: FetcherConfig<TOutput>): Fetcher<TOutput>;
/**
* Initiates a redirect to connect the user's account with a specified connection.
* This method generates PKCE parameters, creates a transaction, and redirects to the /connect endpoint.
*
* You must enable `Offline Access` from the Connection Permissions settings to be able to use the connection with Connected Accounts.
*
* @template TAppState - The application state to persist through the transaction.
* @param {RedirectConnectAccountOptions<TAppState>} options - Options for the connect account redirect flow.
* @param {string} options.connection - The name of the connection to link (e.g. 'google-oauth2').
* @param {string[]} [options.scopes] - Array of scopes to request from the Identity Provider during the connect account flow.
* @param {AuthorizationParams} [options.authorization_params] - Additional authorization parameters for the request to the upstream IdP.
* @param {string} [options.redirectUri] - The URI to redirect back to after connecting the account.
* @param {TAppState} [options.appState] - Application state to persist through the transaction.
* @param {(url: string) => Promise<void>} [options.openUrl] - Custom function to open the URL.
*
* @returns {Promise<void>} Resolves when the redirect is initiated.
* @throws {MyAccountApiError} If the connect request to the My Account API fails.
*/
connectAccountWithRedirect<TAppState = any>(options: RedirectConnectAccountOptions<TAppState>): Promise<void>;
}
+78
View File
@@ -0,0 +1,78 @@
import { ICache } from './cache';
import { Auth0ClientOptions, AuthorizationParams, AuthorizeOptions, ClientAuthorizationParams, LogoutOptions } from './global';
/**
* @ignore
*/
export declare const GET_TOKEN_SILENTLY_LOCK_KEY = "auth0.lock.getTokenSilently";
/**
* @ignore
*/
export declare const buildGetTokenSilentlyLockKey: (clientId: string, audience: string) => string;
/**
* @ignore
*/
export declare const buildOrganizationHintCookieName: (clientId: string) => string;
/**
* @ignore
*/
export declare const OLD_IS_AUTHENTICATED_COOKIE_NAME = "auth0.is.authenticated";
/**
* @ignore
*/
export declare const buildIsAuthenticatedCookieName: (clientId: string) => string;
/**
* @ignore
*/
export declare const cacheFactory: (location: string) => () => ICache;
/**
* @ignore
*/
export declare const getAuthorizeParams: (clientOptions: Auth0ClientOptions & {
authorizationParams: ClientAuthorizationParams;
}, scope: Record<string, string>, authorizationParams: AuthorizationParams & {
scope?: string | undefined;
}, state: string, nonce: string, code_challenge: string, redirect_uri: string | undefined, response_mode: string | undefined, thumbprint: string | undefined) => AuthorizeOptions;
/**
* @ignore
*
* Function used to provide support for the deprecated onRedirect through openUrl.
*/
export declare const patchOpenUrlWithOnRedirect: <T extends Pick<LogoutOptions, "openUrl" | "onRedirect">>(options: T) => T;
/**
* @ignore
*
* Checks if all scopes are included inside other array of scopes
*/
export declare const allScopesAreIncluded: (scopeToInclude?: string, scopes?: string) => boolean;
/**
* @ignore
*
* Returns the scopes that are missing after a refresh
*/
export declare const getMissingScopes: (requestedScope?: string, respondedScope?: string) => string;
/**
* @ignore
*
* For backward compatibility we are going to check if we are going to downscope while doing a refresh request
* while MRRT is allowed. If the audience is the same for the refresh_token we are going to use and it has
* lower scopes than the ones originally in the token, we are going to return the scopes that were stored
* with the refresh_token in the tokenset.
* @param useMrrt Setting that the user can activate to use MRRT in their requests
* @param authorizationParams Contains the audience and scope that the user requested to obtain a token
* @param cachedAudience Audience stored with the refresh_token wich we are going to use in the request
* @param cachedScope Scope stored with the refresh_token wich we are going to use in the request
*/
export declare const getScopeToRequest: (useMrrt: boolean | undefined, authorizationParams: {
audience?: string;
scope: string;
}, cachedAudience?: string, cachedScope?: string) => string;
/**
* @ignore
*
* Checks if the refresh request has been done using MRRT
* @param cachedAudience Audience from the refresh token used to refresh
* @param cachedScope Scopes from the refresh token used to refresh
* @param requestAudience Audience sent to the server
* @param requestScope Scopes sent to the server
*/
export declare const isRefreshWithMrrt: (cachedAudience: string | undefined, cachedScope: string | undefined, requestAudience: string | undefined, requestScope: string) => boolean;
+92
View File
@@ -0,0 +1,92 @@
import { AuthorizationParams } from './global';
import { Fetcher } from './fetcher';
interface ConnectRequest {
/** The name of the connection to link the account with (e.g., 'google-oauth2', 'facebook'). */
connection: string;
/** Array of scopes to request from the Identity Provider during the connect account flow. */
scopes?: string[];
/** The URI to redirect to after the connection process completes. */
redirect_uri: string;
/** An opaque value used to maintain state between the request and callback. */
state?: string;
/** The PKCE code challenge derived from the code verifier. */
code_challenge?: string;
/** The method used to derive the code challenge. Required when code_challenge is provided. */
code_challenge_method?: 'S256';
authorization_params?: AuthorizationParams;
}
interface ConnectResponse {
/** The base URI to initiate the account connection flow. */
connect_uri: string;
/** The authentication session identifier. */
auth_session: string;
/** Parameters to be used with the connect URI. */
connect_params: {
/** The ticket identifier to be used with the connection URI. */
ticket: string;
};
/** The number of seconds until the ticket expires. */
expires_in: number;
}
interface CompleteRequest {
/** The authentication session identifier */
auth_session: string;
/** The authorization code returned from the connect flow */
connect_code: string;
/** The redirect URI used in the original request */
redirect_uri: string;
/** The PKCE code verifier */
code_verifier?: string;
}
export interface CompleteResponse {
/** The unique identifier of the connected account */
id: string;
/** The connection name */
connection: string;
/** The access type, always 'offline' */
access_type: 'offline';
/** Array of scopes granted */
scopes?: string[];
/** ISO date string of when the connected account was created */
created_at: string;
/** ISO date string of when the refresh token expires (optional) */
expires_at?: string;
}
export interface ErrorResponse {
type: string;
status: number;
title: string;
detail: string;
validation_errors?: {
detail: string;
field?: string;
pointer?: string;
source?: string;
}[];
}
/**
* Subset of the MyAccount API that handles the connect accounts flow.
*/
export declare class MyAccountApiClient {
private myAccountFetcher;
private apiBase;
constructor(myAccountFetcher: Fetcher<Response>, apiBase: string);
/**
* Get a ticket for the connect account flow.
*/
connectAccount(params: ConnectRequest): Promise<ConnectResponse>;
/**
* Verify the redirect from the connect account flow and complete the connecting of the account.
*/
completeAccount(params: CompleteRequest): Promise<CompleteResponse>;
private _handleResponse;
}
export declare class MyAccountApiError extends Error {
readonly type: string;
readonly status: number;
readonly title: string;
readonly detail: string;
readonly validation_errors?: ErrorResponse['validation_errors'];
constructor({ type, status, title, detail, validation_errors }: ErrorResponse);
}
export {};
+71
View File
@@ -0,0 +1,71 @@
/**
* Represents the configuration options required for initiating a Custom Token Exchange request
* following RFC 8693 specifications.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc8693 | RFC 8693: OAuth 2.0 Token Exchange}
*/
export type CustomTokenExchangeOptions = {
/**
* The type identifier for the subject token being exchanged
*
* @pattern
* - Must be a namespaced URI under your organization's control
* - Forbidden patterns:
* - `^urn:ietf:params:oauth:*` (IETF reserved)
* - `^https:\/\/auth0\.com/*` (Auth0 reserved)
* - `^urn:auth0:*` (Auth0 reserved)
*
* @example
* "urn:acme:legacy-system-token"
* "https://api.yourcompany.com/token-type/v1"
*/
subject_token_type: string;
/**
* The opaque token value being exchanged for Auth0 tokens
*
* @security
* - Must be validated in Auth0 Actions using strong cryptographic verification
* - Implement replay attack protection
* - Recommended validation libraries: `jose`, `jsonwebtoken`
*
* @example
* "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
*/
subject_token: string;
/**
* The target audience for the requested Auth0 token
*
* @remarks
* Must match exactly with an API identifier configured in your Auth0 tenant.
* If not provided, falls back to the client's default audience.
*
* @example
* "https://api.your-service.com/v1"
*/
audience?: string;
/**
* Space-separated list of OAuth 2.0 scopes being requested
*
* @remarks
* Subject to API authorization policies configured in Auth0
*
* @example
* "openid profile email read:data write:data"
*/
scope?: string;
/**
* Additional custom parameters for Auth0 Action processing
*
* @remarks
* Accessible in Action code via `event.request.body`
*
* @example
* ```typescript
* {
* custom_parameter: "session_context",
* device_fingerprint: "a3d8f7...",
* }
* ```
*/
[key: string]: unknown;
};
+2
View File
@@ -0,0 +1,2 @@
import { TokenEndpointOptions, TokenEndpointResponse } from './global';
export declare function oauthToken({ baseUrl, timeout, audience, scope, auth0Client, useFormData, useMrrt, dpop, ...options }: TokenEndpointOptions, worker?: Worker): Promise<TokenEndpointResponse>;
@@ -0,0 +1,7 @@
import { ICache, Cacheable, MaybePromise } from './shared';
export declare class LocalStorageCache implements ICache {
set<T = Cacheable>(key: string, entry: T): void;
get<T = Cacheable>(key: string): MaybePromise<T | undefined>;
remove(key: string): void;
allKeys(): string[];
}
+52
View File
@@ -0,0 +1,52 @@
import { CacheKeyManifest } from './key-manifest';
import { CacheEntry, ICache, CacheKey, DecodedToken, IdTokenEntry } from './shared';
export declare class CacheManager {
private cache;
private keyManifest?;
private nowProvider;
constructor(cache: ICache, keyManifest?: CacheKeyManifest | undefined, nowProvider?: () => number | Promise<number>);
setIdToken(clientId: string, idToken: string, decodedToken: DecodedToken): Promise<void>;
getIdToken(cacheKey: CacheKey): Promise<IdTokenEntry | undefined>;
get(cacheKey: CacheKey, expiryAdjustmentSeconds?: number, useMrrt?: boolean, cacheMode?: string): Promise<Partial<CacheEntry> | undefined>;
private modifiedCachedEntry;
set(entry: CacheEntry): Promise<void>;
remove(client_id: string, audience?: string, scope?: string): Promise<void>;
clear(clientId?: string): Promise<void>;
private wrapCacheEntry;
private getCacheKeys;
/**
* Returns the cache key to be used to store the id token
* @param clientId The client id used to link to the id token
* @returns The constructed cache key, as a string, to store the id token
*/
private getIdTokenCacheKey;
/**
* Finds the corresponding key in the cache based on the provided cache key.
* The keys inside the cache are in the format {prefix}::{clientId}::{audience}::{scope}.
* The first key in the cache that satisfies the following conditions is returned
* - `prefix` is strict equal to Auth0's internally configured `keyPrefix`
* - `clientId` is strict equal to the `cacheKey.clientId`
* - `audience` is strict equal to the `cacheKey.audience`
* - `scope` contains at least all the `cacheKey.scope` values
* *
* @param keyToMatch The provided cache key
* @param allKeys A list of existing cache keys
*/
private matchExistingCacheKey;
/**
* Returns the first entry that contains a refresh_token that satisfies the following conditions
* The keys inside the cache are in the format {prefix}::{clientId}::{audience}::{scope}.
* - `prefix` is strict equal to Auth0's internally configured `keyPrefix`
* - `clientId` is strict equal to the `cacheKey.clientId`
* @param keyToMatch The provided cache key
* @param allKeys A list of existing cache keys
*/
private getEntryWithRefreshToken;
/**
* Updates in the cache all entries that has a match with previous refresh_token with the
* new refresh_token obtained from the server
* @param oldRefreshToken Old refresh_token used on refresh
* @param newRefreshToken New refresh_token obtained from the server after refresh
*/
updateEntry(oldRefreshToken: string, newRefreshToken: string): Promise<void>;
}
@@ -0,0 +1,4 @@
import { ICache } from './shared';
export declare class InMemoryCache {
enclosedCache: ICache;
}
+4
View File
@@ -0,0 +1,4 @@
export * from './cache-localstorage';
export * from './cache-memory';
export * from './cache-manager';
export * from './shared';
+12
View File
@@ -0,0 +1,12 @@
import { ICache, KeyManifestEntry, MaybePromise } from './shared';
export declare class CacheKeyManifest {
private cache;
private clientId;
private readonly manifestKey;
constructor(cache: ICache, clientId: string);
add(key: string): Promise<void>;
remove(key: string): Promise<void>;
get(): MaybePromise<KeyManifestEntry | undefined>;
clear(): MaybePromise<void>;
private createManifestKeyFrom;
}
+68
View File
@@ -0,0 +1,68 @@
import { IdToken, User } from '../global';
export declare const CACHE_KEY_PREFIX = "@@auth0spajs@@";
export declare const CACHE_KEY_ID_TOKEN_SUFFIX = "@@user@@";
export type CacheKeyData = {
audience?: string;
scope?: string;
clientId: string;
};
export declare class CacheKey {
prefix: string;
suffix?: string | undefined;
clientId: string;
scope?: string;
audience?: string;
constructor(data: CacheKeyData, prefix?: string, suffix?: string | undefined);
/**
* Converts this `CacheKey` instance into a string for use in a cache
* @returns A string representation of the key
*/
toKey(): string;
/**
* Converts a cache key string into a `CacheKey` instance.
* @param key The key to convert
* @returns An instance of `CacheKey`
*/
static fromKey(key: string): CacheKey;
/**
* Utility function to build a `CacheKey` instance from a cache entry
* @param entry The entry
* @returns An instance of `CacheKey`
*/
static fromCacheEntry(entry: CacheEntry): CacheKey;
}
export interface DecodedToken {
claims: IdToken;
user: User;
}
export interface IdTokenEntry {
id_token: string;
decodedToken: DecodedToken;
}
export type CacheEntry = {
id_token?: string;
token_type?: string;
access_token: string;
expires_in: number;
decodedToken?: DecodedToken;
audience: string;
scope: string;
client_id: string;
refresh_token?: string;
oauthTokenScope?: string;
};
export type WrappedCacheEntry = {
body: Partial<CacheEntry>;
expiresAt: number;
};
export type KeyManifestEntry = {
keys: string[];
};
export type Cacheable = WrappedCacheEntry | KeyManifestEntry;
export type MaybePromise<T> = Promise<T> | T;
export interface ICache {
set<T = Cacheable>(key: string, entry: T): MaybePromise<void>;
get<T = Cacheable>(key: string): MaybePromise<T | undefined>;
remove(key: string): MaybePromise<void>;
allKeys?(): MaybePromise<string[]>;
}
+48
View File
@@ -0,0 +1,48 @@
import { PopupConfigOptions } from './global';
/**
* @ignore
*/
export declare const DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS = 60;
/**
* @ignore
*/
export declare const DEFAULT_POPUP_CONFIG_OPTIONS: PopupConfigOptions;
/**
* @ignore
*/
export declare const DEFAULT_SILENT_TOKEN_RETRY_COUNT = 3;
/**
* @ignore
*/
export declare const CLEANUP_IFRAME_TIMEOUT_IN_SECONDS = 2;
/**
* @ignore
*/
export declare const DEFAULT_FETCH_TIMEOUT_MS = 10000;
export declare const CACHE_LOCATION_MEMORY = "memory";
export declare const CACHE_LOCATION_LOCAL_STORAGE = "localstorage";
/**
* @ignore
*/
export declare const MISSING_REFRESH_TOKEN_ERROR_MESSAGE = "Missing Refresh Token";
/**
* @ignore
*/
export declare const INVALID_REFRESH_TOKEN_ERROR_MESSAGE = "invalid refresh token";
/**
* @ignore
*/
export declare const DEFAULT_SCOPE = "openid profile email";
/**
* @ignore
*/
export declare const DEFAULT_SESSION_CHECK_EXPIRY_DAYS = 1;
/**
* @ignore
*/
export declare const DEFAULT_AUTH0_CLIENT: {
name: string;
version: string;
};
export declare const DEFAULT_NOW_PROVIDER: () => number;
export declare const DEFAULT_AUDIENCE = "default";
+17
View File
@@ -0,0 +1,17 @@
import { DpopStorage } from './storage';
import * as dpopUtils from './utils';
export declare class Dpop {
protected readonly storage: DpopStorage;
constructor(clientId: string);
getNonce(id?: string): Promise<string | undefined>;
setNonce(nonce: string, id?: string): Promise<void>;
protected getOrGenerateKeyPair(): Promise<dpopUtils.KeyPair>;
generateProof(params: {
url: string;
method: string;
nonce?: string;
accessToken?: string;
}): Promise<string>;
calculateThumbprint(): Promise<string>;
clear(): Promise<void>;
}
+27
View File
@@ -0,0 +1,27 @@
import { type KeyPair } from './utils';
declare const TABLES: {
readonly NONCE: "nonce";
readonly KEYPAIR: "keypair";
};
type Table = (typeof TABLES)[keyof typeof TABLES];
export declare class DpopStorage {
protected readonly clientId: string;
protected dbHandle: IDBDatabase | undefined;
constructor(clientId: string);
protected getVersion(): number;
protected createDbHandle(): Promise<IDBDatabase>;
protected getDbHandle(): Promise<IDBDatabase>;
protected executeDbRequest<T = unknown>(table: string, mode: IDBTransactionMode, requestFactory: (table: IDBObjectStore) => IDBRequest<T>): Promise<T>;
protected buildKey(id?: string): string;
setNonce(nonce: string, id?: string): Promise<void>;
setKeyPair(keyPair: KeyPair): Promise<void>;
protected save(table: Table, key: IDBValidKey, obj: unknown): Promise<void>;
findNonce(id?: string): Promise<string | undefined>;
findKeyPair(): Promise<KeyPair | undefined>;
protected find<T = unknown>(table: Table, key: IDBValidKey): Promise<T | undefined>;
protected deleteBy(table: Table, predicate: (key: IDBValidKey) => boolean): Promise<void>;
protected deleteByClientId(table: Table, clientId: string): Promise<void>;
clearNonces(): Promise<void>;
clearKeyPairs(): Promise<void>;
}
export {};
+15
View File
@@ -0,0 +1,15 @@
import * as dpopLib from 'dpop';
export declare const DPOP_NONCE_HEADER = "dpop-nonce";
export type KeyPair = Readonly<dpopLib.KeyPair>;
type GenerateProofParams = {
keyPair: KeyPair;
url: string;
method: string;
nonce?: string;
accessToken?: string;
};
export declare function generateKeyPair(): Promise<KeyPair>;
export declare function calculateThumbprint(keyPair: Pick<KeyPair, 'publicKey'>): Promise<string>;
export declare function generateProof({ keyPair, url, method, nonce, accessToken }: GenerateProofParams): Promise<string>;
export declare function isGrantTypeSupported(grantType: string): boolean;
export {};
+79
View File
@@ -0,0 +1,79 @@
/**
* Thrown when network requests to the Auth server fail.
*/
export declare class GenericError extends Error {
error: string;
error_description: string;
constructor(error: string, error_description: string);
static fromPayload({ error, error_description }: {
error: string;
error_description: string;
}): GenericError;
}
/**
* Thrown when handling the redirect callback fails, will be one of Auth0's
* Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses
*/
export declare class AuthenticationError extends GenericError {
state: string;
appState: any;
constructor(error: string, error_description: string, state: string, appState?: any);
}
/**
* Thrown when handling the redirect callback for the connect flow fails, will be one of Auth0's
* Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses
*/
export declare class ConnectError extends GenericError {
connection: string;
state: string;
appState: any;
constructor(error: string, error_description: string, connection: string, state: string, appState?: any);
}
/**
* Thrown when silent auth times out (usually due to a configuration issue) or
* when network requests to the Auth server timeout.
*/
export declare class TimeoutError extends GenericError {
constructor();
}
/**
* Error thrown when the login popup times out (if the user does not complete auth)
*/
export declare class PopupTimeoutError extends TimeoutError {
popup: Window;
constructor(popup: Window);
}
export declare class PopupCancelledError extends GenericError {
popup: Window;
constructor(popup: Window);
}
/**
* Error thrown when the token exchange results in a `mfa_required` error
*/
export declare class MfaRequiredError extends GenericError {
mfa_token: string;
constructor(error: string, error_description: string, mfa_token: string);
}
/**
* Error thrown when there is no refresh token to use
*/
export declare class MissingRefreshTokenError extends GenericError {
audience: string;
scope: string;
constructor(audience: string, scope: string);
}
/**
* Error thrown when there are missing scopes after refreshing a token
*/
export declare class MissingScopesError extends GenericError {
audience: string;
scope: string;
constructor(audience: string, scope: string);
}
/**
* Error thrown when the wrong DPoP nonce is used and a potential subsequent retry wasn't able to fix it.
*/
export declare class UseDpopNonceError extends GenericError {
newDpopNonce: string | undefined;
constructor(newDpopNonce: string | undefined);
}
+54
View File
@@ -0,0 +1,54 @@
import { GetTokenSilentlyVerboseResponse } from './global';
export type ResponseHeaders = Record<string, string | null | undefined> | [string, string][] | {
get(name: string): string | null | undefined;
};
export type CustomFetchMinimalOutput = {
status: number;
headers: ResponseHeaders;
};
export type CustomFetchImpl<TOutput extends CustomFetchMinimalOutput> = (req: Request) => Promise<TOutput>;
export type AuthParams = {
scope?: string[];
audience?: string;
};
type AccessTokenFactory = (authParams?: AuthParams) => Promise<string | GetTokenSilentlyVerboseResponse>;
export type FetcherConfig<TOutput extends CustomFetchMinimalOutput> = {
getAccessToken?: AccessTokenFactory;
baseUrl?: string;
fetch?: CustomFetchImpl<TOutput>;
dpopNonceId?: string;
};
export type FetcherHooks = {
isDpopEnabled: () => boolean;
getAccessToken: AccessTokenFactory;
getDpopNonce: () => Promise<string | undefined>;
setDpopNonce: (nonce: string) => Promise<void>;
generateDpopProof: (params: {
url: string;
method: string;
nonce?: string;
accessToken: string;
}) => Promise<string>;
};
export type FetchWithAuthCallbacks<TOutput> = {
onUseDpopNonceError?(): Promise<TOutput>;
};
export declare class Fetcher<TOutput extends CustomFetchMinimalOutput> {
protected readonly config: Omit<FetcherConfig<TOutput>, 'fetch'> & Required<Pick<FetcherConfig<TOutput>, 'fetch'>>;
protected readonly hooks: FetcherHooks;
constructor(config: FetcherConfig<TOutput>, hooks: FetcherHooks);
protected isAbsoluteUrl(url: string): boolean;
protected buildUrl(baseUrl: string | undefined, url: string | undefined): string;
protected getAccessToken(authParams?: AuthParams): Promise<string | GetTokenSilentlyVerboseResponse>;
protected extractUrl(info: RequestInfo | URL): string;
protected buildBaseRequest(info: RequestInfo | URL, init: RequestInit | undefined): Request;
protected setAuthorizationHeader(request: Request, accessToken: string, tokenType?: string): void;
protected setDpopProofHeader(request: Request, accessToken: string): Promise<void>;
protected prepareRequest(request: Request, authParams?: AuthParams): Promise<void>;
protected getHeader(headers: ResponseHeaders, name: string): string;
protected hasUseDpopNonceError(response: TOutput): boolean;
protected handleResponse(response: TOutput, callbacks: FetchWithAuthCallbacks<TOutput>): Promise<TOutput>;
protected internalFetchWithAuth(info: RequestInfo | URL, init: RequestInit | undefined, callbacks: FetchWithAuthCallbacks<TOutput>, authParams?: AuthParams): Promise<TOutput>;
fetchWithAuth(info: RequestInfo | URL, init?: RequestInit, authParams?: AuthParams): Promise<TOutput>;
}
export {};
+715
View File
@@ -0,0 +1,715 @@
import { ICache } from './cache';
import type { Dpop } from './dpop/dpop';
import { CompleteResponse } from './MyAccountApiClient';
export interface AuthorizationParams {
/**
* - `'page'`: displays the UI with a full page view
* - `'popup'`: displays the UI with a popup window
* - `'touch'`: displays the UI in a way that leverages a touch interface
* - `'wap'`: displays the UI with a "feature phone" type interface
*/
display?: 'page' | 'popup' | 'touch' | 'wap';
/**
* - `'none'`: do not prompt user for login or consent on reauthentication
* - `'login'`: prompt user for reauthentication
* - `'consent'`: prompt user for consent before processing request
* - `'select_account'`: prompt user to select an account
*/
prompt?: 'none' | 'login' | 'consent' | 'select_account';
/**
* Maximum allowable elapsed time (in seconds) since authentication.
* If the last time the user authenticated is greater than this value,
* the user must be reauthenticated.
*/
max_age?: string | number;
/**
* The space-separated list of language tags, ordered by preference.
* For example: `'fr-CA fr en'`.
*/
ui_locales?: string;
/**
* Previously issued ID Token.
*/
id_token_hint?: string;
/**
* Provides a hint to Auth0 as to what flow should be displayed.
* The default behavior is to show a login page but you can override
* this by passing 'signup' to show the signup page instead.
*
* This only affects the New Universal Login Experience.
*/
screen_hint?: 'signup' | 'login' | string;
/**
* The user's email address or other identifier. When your app knows
* which user is trying to authenticate, you can provide this parameter
* to pre-fill the email box or select the right session for sign-in.
*
* This currently only affects the classic Lock experience.
*/
login_hint?: string;
acr_values?: string;
/**
* The default scope to be used on authentication requests.
*
* This defaults to `profile email` if not set. If you are setting extra scopes and require
* `profile` and `email` to be included then you must include them in the provided scope.
*
* Note: The `openid` scope is **always applied** regardless of this setting.
*/
scope?: string;
/**
* The default audience to be used for requesting API access.
*/
audience?: string;
/**
* The name of the connection configured for your application.
* If null, it will redirect to the Auth0 Login Page and show
* the Login Widget.
*/
connection?: string;
/**
* The organization to log in to.
*
* This will specify an `organization` parameter in your user's login request.
*
* - If you provide an Organization ID (a string with the prefix `org_`), it will be validated against the `org_id` claim of your user's ID Token. The validation is case-sensitive.
* - If you provide an Organization Name (a string *without* the prefix `org_`), it will be validated against the `org_name` claim of your user's ID Token. The validation is case-insensitive.
* To use an Organization Name you must have "Allow Organization Names in Authentication API" switched on in your Auth0 settings dashboard.
* More information is available on the [Auth0 documentation portal](https://auth0.com/docs/manage-users/organizations/configure-organizations/use-org-name-authentication-api)
*
*/
organization?: string;
/**
* The Id of an invitation to accept. This is available from the user invitation URL that is given when participating in a user invitation flow.
*/
invitation?: string;
/**
* The default URL where Auth0 will redirect your browser to with
* the authentication result. It must be whitelisted in
* the "Allowed Callback URLs" field in your Auth0 Application's
* settings. If not provided here, it should be provided in the other
* methods that provide authentication.
*/
redirect_uri?: string;
/**
* If you need to send custom parameters to the Authorization Server,
* make sure to use the original parameter name.
*/
[key: string]: any;
}
export interface ClientAuthorizationParams extends Omit<AuthorizationParams, 'scope'> {
scope?: string | Record<string, string>;
}
interface BaseLoginOptions {
/**
* URL parameters that will be sent back to the Authorization Server. This can be known parameters
* defined by Auth0 or custom parameters that you define.
*/
authorizationParams?: AuthorizationParams;
}
export interface Auth0ClientOptions {
/**
* Your Auth0 account domain such as `'example.auth0.com'`,
* `'example.eu.auth0.com'` or , `'example.mycompany.com'`
* (when using [custom domains](https://auth0.com/docs/custom-domains))
*/
domain: string;
/**
* The issuer to be used for validation of JWTs, optionally defaults to the domain above
*/
issuer?: string;
/**
* The Client ID found on your Application settings page
*/
clientId: string;
/**
* The value in seconds used to account for clock skew in JWT expirations.
* Typically, this value is no more than a minute or two at maximum.
* Defaults to 60s.
*/
leeway?: number;
/**
* The location to use when storing cache data. Valid values are `memory` or `localstorage`.
* The default setting is `memory`.
*
* Read more about [changing storage options in the Auth0 docs](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options)
*/
cacheLocation?: CacheLocation;
/**
* Specify a custom cache implementation to use for token storage and retrieval. This setting takes precedence over `cacheLocation` if they are both specified.
*/
cache?: ICache;
/**
* If true, refresh tokens are used to fetch new access tokens from the Auth0 server. If false, the legacy technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` is used.
* The default setting is `false`.
*
* **Note**: Use of refresh tokens must be enabled by an administrator on your Auth0 client application.
*/
useRefreshTokens?: boolean;
/**
* 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. If false, the iframe fallback is not used and
* errors relating to a failed `refresh_token` grant should be handled appropriately. The default setting is `false`.
*
* **Note**: There might be situations where doing silent auth with a Web Message response from an iframe is not possible,
* like when you're serving your application from the file system or a custom protocol (like in a Desktop or Native app).
* In situations like this you can disable the iframe fallback and handle the failed `refresh_token` grant and prompt the user to login interactively with `loginWithRedirect` or `loginWithPopup`."
*
* E.g. Using the `file:` protocol in an Electron application does not support that legacy technique.
*
* @example
* let token: string;
* try {
* token = await auth0.getTokenSilently();
* } catch (e) {
* if (e.error === 'missing_refresh_token' || e.error === 'invalid_grant') {
* auth0.loginWithRedirect();
* }
* }
*/
useRefreshTokensFallback?: boolean;
/**
* A maximum number of seconds to wait before declaring background calls to /authorize as failed for timeout
* Defaults to 60s.
*/
authorizeTimeoutInSeconds?: number;
/**
* Specify the timeout for HTTP calls using `fetch`. The default is 10 seconds.
*/
httpTimeoutInSeconds?: number;
/**
* Internal property to send information about the client to the authorization server.
* @internal
*/
auth0Client?: {
name: string;
version: string;
env?: {
[key: string]: string;
};
};
/**
* Sets an additional cookie with no SameSite attribute to support legacy browsers
* that are not compatible with the latest SameSite changes.
* This will log a warning on modern browsers, you can disable the warning by setting
* this to false but be aware that some older useragents will not work,
* See https://www.chromium.org/updates/same-site/incompatible-clients
* Defaults to true
*/
legacySameSiteCookie?: boolean;
/**
* If `true`, the SDK will use a cookie when storing information about the auth transaction while
* the user is going through the authentication flow on the authorization server.
*
* The default is `false`, in which case the SDK will use session storage.
*
* @notes
*
* You might want to enable this if you rely on your users being able to authenticate using flows that
* may end up spanning across multiple tabs (e.g. magic links) or you cannot otherwise rely on session storage being available.
*/
useCookiesForTransactions?: boolean;
/**
* Number of days until the cookie `auth0.is.authenticated` will expire
* Defaults to 1.
*/
sessionCheckExpiryDays?: number;
/**
* The domain the cookie is accessible from. If not set, the cookie is scoped to
* the current domain, including the subdomain.
*
* Note: setting this incorrectly may cause silent authentication to stop working
* on page load.
*
*
* To keep a user logged in across multiple subdomains set this to your
* top-level domain and prefixed with a `.` (eg: `.example.com`).
*/
cookieDomain?: string;
/**
* If true, data to the token endpoint is transmitted as x-www-form-urlencoded data, if false it will be transmitted as JSON. The default setting is `true`.
*
* **Note:** Setting this to `false` may affect you if you use Auth0 Rules and are sending custom, non-primitive data. If you disable this,
* please verify that your Auth0 Rules continue to work as intended.
*/
useFormData?: boolean;
/**
* Modify the value used as the current time during the token validation.
*
* **Note**: Using this improperly can potentially compromise the token validation.
*/
nowProvider?: () => Promise<number> | number;
/**
* If provided, the SDK will load the token worker from this URL instead of the integrated `blob`. An example of when this is useful is if you have strict
* Content-Security-Policy (CSP) and wish to avoid needing to set `worker-src: blob:`. We recommend either serving the worker, which you can find in the module
* at `<module_path>/dist/auth0-spa-js.worker.production.js`, from the same host as your application or using the Auth0 CDN
* `https://cdn.auth0.com/js/auth0-spa-js/<version>/auth0-spa-js.worker.production.js`.
*
* **Note**: The worker is only used when `useRefreshTokens: true`, `cacheLocation: 'memory'`, and the `cache` is not custom.
*/
workerUrl?: string;
/**
* If `true`, the SDK will allow the refreshing of tokens using MRRT
*/
useMrrt?: boolean;
/**
* If `true`, DPoP (OAuth 2.0 Demonstrating Proof of Possession, RFC9449)
* will be used to cryptographically bind tokens to this specific browser
* so they can't be used from a different device in case of a leak.
*
* The default setting is `false`.
*/
useDpop?: boolean;
/**
* URL parameters that will be sent back to the Authorization Server. This can be known parameters
* defined by Auth0 or custom parameters that you define.
*/
authorizationParams?: ClientAuthorizationParams;
}
/**
* The possible locations where tokens can be stored
*/
export type CacheLocation = 'memory' | 'localstorage';
/**
* @ignore
*/
export interface AuthorizeOptions extends AuthorizationParams {
response_type: string;
response_mode: string;
redirect_uri?: string;
nonce: string;
state: string;
scope: string;
code_challenge: string;
code_challenge_method: string;
}
export interface RedirectLoginOptions<TAppState = any> extends BaseLoginOptions {
/**
* Used to store state before doing the redirect
*/
appState?: TAppState;
/**
* Used to add to the URL fragment before redirecting
*/
fragment?: string;
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* @example
* const client = new Auth0Client({
* async onRedirect(url) {
* window.location.replace(url);
* }
* });
* @deprecated since v2.0.1, use `openUrl` instead.
*/
onRedirect?: (url: string) => Promise<void>;
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* @example
* const client = new Auth0Client({
* openUrl(url) {
* window.location.replace(url);
* }
* });
*
* @example
* import { Browser } from '@capacitor/browser';
*
* const client = new Auth0Client({
* async openUrl(url) {
* await Browser.open({ url });
* }
* });
*/
openUrl?: (url: string) => Promise<void> | void;
}
/**
* The types of responses expected from the authorization server.
* - `code`: used for the standard login flow.
* - `connect_code`: used for the connect account flow.
*/
export declare enum ResponseType {
Code = "code",
ConnectCode = "connect_code"
}
export interface RedirectLoginResult<TAppState = any> {
/**
* State stored when the redirect request was made
*/
appState?: TAppState;
/**
* The type of response, for login it will be `code`
*/
response_type: ResponseType.Code;
}
export interface PopupLoginOptions extends BaseLoginOptions {
}
export interface PopupConfigOptions {
/**
* The number of seconds to wait for a popup response before
* throwing a timeout error. Defaults to 60s
*/
timeoutInSeconds?: number;
/**
* Accepts an already-created popup window to use. If not specified, the SDK
* will create its own. This may be useful for platforms like iOS that have
* security restrictions around when popups can be invoked (e.g. from a user click event)
*/
popup?: any;
}
export interface GetTokenSilentlyOptions {
/**
* When `off`, ignores the cache and always sends a
* request to Auth0.
* When `cache-only`, only reads from the cache and never sends a request to Auth0.
* Defaults to `on`, where it both reads from the cache and sends a request to Auth0 as needed.
*/
cacheMode?: 'on' | 'off' | 'cache-only';
/**
* Parameters that will be sent back to Auth0 as part of a request.
*/
authorizationParams?: {
/**
* There's no actual redirect when getting a token silently,
* but, according to the spec, a `redirect_uri` param is required.
* Auth0 uses this parameter to validate that the current `origin`
* matches the `redirect_uri` `origin` when sending the response.
* It must be whitelisted in the "Allowed Web Origins" in your
* Auth0 Application's settings.
*/
redirect_uri?: string;
/**
* The scope that was used in the authentication request
*/
scope?: string;
/**
* The audience that was used in the authentication request
*/
audience?: string;
/**
* If you need to send custom parameters to the Authorization Server,
* make sure to use the original parameter name.
*/
[key: string]: any;
};
/** A maximum number of seconds to wait before declaring the background /authorize call as failed for timeout
* Defaults to 60s.
*/
timeoutInSeconds?: number;
/**
* If true, the full response from the /oauth/token endpoint (or the cache, if the cache was used) is returned
* (minus `refresh_token` if one was issued). Otherwise, just the access token is returned.
*
* The default is `false`.
*/
detailedResponse?: boolean;
}
export interface GetTokenWithPopupOptions extends PopupLoginOptions {
/**
* When `off`, ignores the cache and always sends a request to Auth0.
* When `cache-only`, only reads from the cache and never sends a request to Auth0.
* Defaults to `on`, where it both reads from the cache and sends a request to Auth0 as needed.
*/
cacheMode?: 'on' | 'off' | 'cache-only';
}
export interface LogoutUrlOptions {
/**
* The `clientId` of your application.
*
* If this property is not set, then the `clientId` that was used during initialization of the SDK is sent to the logout endpoint.
*
* If this property is set to `null`, then no client ID value is sent to the logout endpoint.
*
* [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
*/
clientId?: string | null;
/**
* Parameters to pass to the logout endpoint. This can be known parameters defined by Auth0 or custom parameters
* you wish to provide.
*/
logoutParams?: {
/**
* When supported by the upstream identity provider,
* forces the user to logout of their identity provider
* and from Auth0.
* [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps)
*/
federated?: boolean;
/**
* The URL where Auth0 will redirect your browser to after the logout.
*
* **Note**: If the `client_id` parameter is included, the
* `returnTo` URL that is provided must be listed in the
* Application's "Allowed Logout URLs" in the Auth0 dashboard.
* However, if the `client_id` parameter is not included, the
* `returnTo` URL must be listed in the "Allowed Logout URLs" at
* the account level in the Auth0 dashboard.
*
* [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
*/
returnTo?: string;
/**
* If you need to send custom parameters to the logout endpoint, make sure to use the original parameter name.
*/
[key: string]: any;
};
}
export interface LogoutOptions extends LogoutUrlOptions {
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* @example
* await auth0.logout({
* async onRedirect(url) {
* window.location.replace(url);
* }
* });
* @deprecated since v2.0.1, use `openUrl` instead.
*/
onRedirect?: (url: string) => Promise<void>;
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* Set to `false` to disable the redirect, or provide a function to handle the actual redirect yourself.
*
* @example
* await auth0.logout({
* openUrl(url) {
* window.location.replace(url);
* }
* });
*
* @example
* import { Browser } from '@capacitor/browser';
*
* await auth0.logout({
* async openUrl(url) {
* await Browser.open({ url });
* }
* });
*/
openUrl?: false | ((url: string) => Promise<void> | void);
}
export interface RedirectConnectAccountOptions<TAppState = any> {
/**
* The name of the connection to link (e.g. 'google-oauth2').
*/
connection: string;
/**
* Array of scopes to request from the Identity Provider during the connect account flow.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'google-oauth2',
* scopes: ['https://www.googleapis.com/auth/calendar']
* });
*/
scopes?: string[];
/**
* Additional authorization parameters for the request.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'github',
* authorization_params: {
* audience: 'https://api.github.com'
* }
* });
*/
authorization_params?: AuthorizationParams;
/**
* The URI to redirect back to after connecting the account.
*/
redirectUri?: string;
/**
* Optional application state to persist through the transaction.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'google-oauth2',
* appState: { returnTo: '/settings' }
* });
*/
appState?: TAppState;
/**
* Optional function to handle the redirect URL.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'google-oauth2',
* openUrl: async (url) => { myBrowserApi.open(url); }
* });
*/
openUrl?: (url: string) => Promise<void>;
}
/**
* The result returned after a successful account connection redirect.
*
* Combines the redirect login result (including any persisted app state)
* with the complete response from the My Account API.
*
* @template TAppState - The type of application state persisted through the transaction.
* @example
* const result = await auth0.connectAccountWithRedirect(options);
* console.log(result.appState); // Access persisted app state
* console.log(result.connection); // The connection of the account you connected to.
* console.log(result.response_type === 'connect_code'); // The response type will be 'connect_code'
*/
export type ConnectAccountRedirectResult<TAppState = any> = CompleteResponse & {
/**
* State stored when the redirect request was made
*/
appState?: TAppState;
/**
* The type of response, for connect account it will be `connect_code`
*/
response_type: ResponseType.ConnectCode;
};
/**
* @ignore
*/
export interface AuthenticationResult {
state: string;
code?: string;
/**
* This is for the redirect from the connect account flow.
*/
connect_code?: string;
error?: string;
error_description?: string;
}
/**
* @ignore
*/
export interface TokenEndpointOptions {
baseUrl: string;
client_id: string;
grant_type: string;
timeout?: number;
auth0Client: any;
useFormData?: boolean;
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>;
[key: string]: any;
}
export type TokenEndpointResponse = {
id_token: string;
token_type: string;
access_token: string;
refresh_token?: string;
expires_in: number;
scope?: string;
};
/**
* @ignore
*/
export interface OAuthTokenOptions extends TokenEndpointOptions {
code_verifier: string;
code: string;
redirect_uri: string;
audience: string;
scope: string;
}
/**
* @ignore
*/
export interface RefreshTokenOptions extends TokenEndpointOptions {
refresh_token: string;
}
/**
* @ignore
*/
export interface JWTVerifyOptions {
iss: string;
aud: string;
id_token: string;
nonce?: string;
leeway?: number;
max_age?: number;
organization?: string;
now?: number;
}
export interface IdToken {
__raw: string;
name?: string;
given_name?: string;
family_name?: string;
middle_name?: string;
nickname?: string;
preferred_username?: string;
profile?: string;
picture?: string;
website?: string;
email?: string;
email_verified?: boolean;
gender?: string;
birthdate?: string;
zoneinfo?: string;
locale?: string;
phone_number?: string;
phone_number_verified?: boolean;
address?: string;
updated_at?: string;
iss?: string;
aud?: string;
exp?: number;
nbf?: number;
iat?: number;
jti?: string;
azp?: string;
nonce?: string;
auth_time?: string;
at_hash?: string;
c_hash?: string;
acr?: string;
amr?: string[];
sub_jwk?: string;
cnf?: string;
sid?: string;
org_id?: string;
org_name?: string;
[key: string]: any;
}
export declare class User {
name?: string;
given_name?: string;
family_name?: string;
middle_name?: string;
nickname?: string;
preferred_username?: string;
profile?: string;
picture?: string;
website?: string;
email?: string;
email_verified?: boolean;
gender?: string;
birthdate?: string;
zoneinfo?: string;
locale?: string;
phone_number?: string;
phone_number_verified?: boolean;
address?: string;
updated_at?: string;
sub?: string;
[key: string]: any;
}
/**
* @ignore
*/
export type FetchOptions = {
method?: string;
headers?: Record<string, string>;
credentials?: 'include' | 'omit';
body?: string;
signal?: AbortSignal;
};
/**
* @ignore
*/
export type FetchResponse = {
ok: boolean;
headers: Record<string, string | undefined>;
json: any;
};
export type GetTokenSilentlyVerboseResponse = Omit<TokenEndpointResponse, 'refresh_token'>;
export {};
+5
View File
@@ -0,0 +1,5 @@
import { FetchOptions } from './global';
import { Dpop } from './dpop/dpop';
export declare const createAbortController: () => AbortController;
export declare const switchFetch: (fetchUrl: string, audience: string, scope: string, fetchOptions: FetchOptions, worker?: Worker, useFormData?: boolean, timeout?: number, useMrrt?: boolean) => Promise<any>;
export declare function getJSON<T>(url: string, timeout: number | undefined, audience: string, scope: string, options: FetchOptions, worker?: Worker, useFormData?: boolean, useMrrt?: boolean, dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>, isDpopRetry?: boolean): Promise<T>;
+20
View File
@@ -0,0 +1,20 @@
import { Auth0Client } from './Auth0Client';
import { Auth0ClientOptions } from './global';
import './global';
export * from './global';
/**
* Asynchronously creates the Auth0Client instance and calls `checkSession`.
*
* **Note:** There are caveats to using this in a private browser tab, which may not silently authenticae
* a user on page refresh. Please see [the checkSession docs](https://auth0.github.io/auth0-spa-js/classes/Auth0Client.html#checksession) for more info.
*
* @param options The client options
* @returns An instance of Auth0Client
*/
export declare function createAuth0Client(options: Auth0ClientOptions): Promise<Auth0Client>;
export { Auth0Client };
export { ConnectError, GenericError, AuthenticationError, TimeoutError, PopupTimeoutError, PopupCancelledError, MfaRequiredError, MissingRefreshTokenError, UseDpopNonceError } from './errors';
export { ICache, LocalStorageCache, InMemoryCache, Cacheable, DecodedToken, CacheEntry, WrappedCacheEntry, KeyManifestEntry, MaybePromise, CacheKey, CacheKeyData } from './cache';
export { type FetcherConfig } from './fetcher';
export { MyAccountApiError } from './MyAccountApiClient';
export { CustomTokenExchangeOptions } from './TokenExchange';
+21
View File
@@ -0,0 +1,21 @@
import { IdToken, JWTVerifyOptions } from './global';
export declare const decode: (token: string) => {
encoded: {
header: string;
payload: string;
signature: string;
};
header: any;
claims: IdToken;
user: any;
};
export declare const verify: (options: JWTVerifyOptions) => {
encoded: {
header: string;
payload: string;
signature: string;
};
header: any;
claims: IdToken;
user: any;
};
+2
View File
@@ -0,0 +1,2 @@
export declare const singlePromise: <T>(cb: () => Promise<T>, key: string) => Promise<T>;
export declare const retryPromise: (cb: () => Promise<boolean>, maxNumberOfRetries?: number) => Promise<boolean>;
+35
View File
@@ -0,0 +1,35 @@
/**
* @ignore
*/
/**
* Returns a string of unique scopes by removing duplicates and unnecessary whitespace.
*
* @param {...(string | undefined)[]} scopes - A list of scope strings or undefined values.
* @returns {string} A string containing unique scopes separated by a single space.
*/
export declare const getUniqueScopes: (...scopes: (string | undefined)[]) => string;
/**
* @ignore
*/
/**
* We will check if the developer has created the client with a string or object of audience:scopes. We will inject
* the base scopes to each audience, and store the base ones inside default key. As well, if the developer created the Auth0Client
* with a string of scopes, we will store the requested ones with the base scopes inside the default key as well.
* @param authScopes The scopes requested by the user when creating the Auth0Client
* @param openIdScope openId scope
* @param extraScopes Other scopes to accumulate such as offline_access
* @returns {Record<string, string>} An object with all scopes that are going to be accumulated.
*/
export declare const injectDefaultScopes: (authScopes: string | Record<string, string> | undefined, openIdScope: string, ...extraScopes: string[]) => Record<string, string>;
/**
* @ignore
*/
/**
* Will return a string of scopes. If a specific audience was requested and it exist inside the scopes object, we will return those
* related to that audience that we want to accumulate. If not, we will return the ones stored inside the default key.
* @param authScopes Object of audience:scopes that are going to be accumulated
* @param methodScopes The scopes requested for the developer in a specific request
* @param audience The audience the developer requested for an specific request or the one they configured in the Auth0Client
* @returns {string} A combination of Auth0Client scopes and the ones requested by the developer for a specific request
*/
export declare const scopesToRequest: (authScopes: Record<string, string>, methodScopes: string | undefined, audience: string | undefined) => string;
+26
View File
@@ -0,0 +1,26 @@
interface ClientStorageOptions {
daysUntilExpire?: number;
cookieDomain?: string;
}
/**
* Defines a type that handles storage to/from a storage location
*/
export type ClientStorage = {
get<T extends Object>(key: string): T | undefined;
save(key: string, value: any, options?: ClientStorageOptions): void;
remove(key: string, options?: ClientStorageOptions): void;
};
/**
* A storage protocol for marshalling data to/from cookies
*/
export declare const CookieStorage: ClientStorage;
/**
* Cookie storage that creates a cookie for modern and legacy browsers.
* See: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
*/
export declare const CookieStorageWithLegacySameSite: ClientStorage;
/**
* A storage protocol for marshalling data to/from session storage
*/
export declare const SessionStorage: ClientStorage;
export {};
+33
View File
@@ -0,0 +1,33 @@
import { ClientStorage } from './storage';
export interface LoginTransaction {
nonce: string;
scope: string;
audience: string;
appState?: any;
code_verifier: string;
redirect_uri?: string;
organization?: string;
state?: string;
response_type: 'code';
}
export interface ConnectAccountTransaction {
appState?: any;
audience?: string;
auth_session: string;
code_verifier: string;
redirect_uri: string;
scope?: string;
state: string;
connection: string;
response_type: 'connect_code';
}
export declare class TransactionManager {
private storage;
private clientId;
private cookieDomain?;
private storageKey;
constructor(storage: ClientStorage, clientId: string, cookieDomain?: string | undefined);
create<T extends Object = LoginTransaction>(transaction: T): void;
get<T extends Object = LoginTransaction>(): T | undefined;
remove(): void;
}
+35
View File
@@ -0,0 +1,35 @@
import { AuthenticationResult, PopupConfigOptions } from './global';
export declare const parseAuthenticationResult: (queryString: string) => AuthenticationResult;
export declare const runIframe: (authorizeUrl: string, eventOrigin: string, timeoutInSeconds?: number) => Promise<AuthenticationResult>;
export declare const openPopup: (url: string) => Window | null;
export declare const runPopup: (config: PopupConfigOptions) => Promise<AuthenticationResult>;
export declare const getCrypto: () => Crypto;
export declare const createRandomString: () => string;
export declare const encode: (value: string) => string;
export declare const decode: (value: string) => string;
/**
* Strips any property that is not present in ALLOWED_AUTH0CLIENT_PROPERTIES
* @param auth0Client - The full auth0Client object
* @returns The stripped auth0Client object
*/
export declare const stripAuth0Client: (auth0Client: any) => any;
export declare const createQueryParams: ({ clientId: client_id, ...params }: any) => string;
export declare const sha256: (s: string) => Promise<any>;
export declare const urlDecodeB64: (input: string) => string;
export declare const bufferToBase64UrlEncoded: (input: number[] | Uint8Array) => string;
export declare const validateCrypto: () => void;
/**
* @ignore
*/
export declare const getDomain: (domainUrl: string) => string;
/**
* @ignore
*/
export declare const getTokenIssuer: (issuer: string | undefined, domainUrl: string) => string;
export declare const parseNumber: (value: any) => number | undefined;
/**
* Ponyfill for `Object.fromEntries()`, which is not available until ES2020.
*
* When the target of this project reaches ES2020, this can be removed.
*/
export declare const fromEntries: <T = any>(iterable: Iterable<[PropertyKey, T]>) => Record<PropertyKey, T>;
+2
View File
@@ -0,0 +1,2 @@
declare const _default: "2.9.1";
export default _default;
@@ -0,0 +1 @@
export {};
+15
View File
@@ -0,0 +1,15 @@
import { FetchOptions } from '../global';
/**
* @ts-ignore
*/
export type WorkerRefreshTokenMessage = {
timeout: number;
fetchUrl: string;
fetchOptions: FetchOptions;
useFormData?: boolean;
useMrrt?: boolean;
auth: {
audience: string;
scope: string;
};
};
@@ -0,0 +1,7 @@
import { WorkerRefreshTokenMessage } from './worker.types';
/**
* Sends the specified message to the web worker
* @param message The message to send
* @param to The worker to send the message to
*/
export declare const sendMessage: (message: WorkerRefreshTokenMessage, to: Worker) => Promise<unknown>;
+126
View File
@@ -0,0 +1,126 @@
{
"author": "Auth0",
"name": "@auth0/auth0-spa-js",
"description": "Auth0 SDK for Single Page Applications using Authorization Code Grant Flow with PKCE",
"license": "MIT",
"version": "2.9.1",
"main": "dist/lib/auth0-spa-js.cjs.js",
"types": "dist/typings/index.d.ts",
"module": "dist/auth0-spa-js.production.esm.js",
"ccu": {
"name": "auth0-spa-js",
"cdn": "https://cdn.auth0.com",
"bucket": "assets.us.auth0.com",
"localPath": "dist",
"mainBundleFile": "auth0-spa-js.production.js",
"digest": {
"hashes": [
"sha384"
],
"extensions": [
".js"
]
}
},
"dependencies": {
"browser-tabs-lock": "^1.2.15",
"dpop": "^2.1.1",
"es-cookie": "~1.3.2"
},
"scripts": {
"dev": "rimraf dist && rollup -c --watch",
"start": "npm run dev",
"docs": "typedoc --options ./typedoc.js src",
"build": "rimraf dist && rollup -m -c --environment NODE_ENV:production && npm run test:es-check",
"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",
"lint": "eslint --ext .jsx,.js src/",
"lint:security": "eslint ./src --ext ts --no-eslintrc --config ./.eslintrc.security",
"test": "jest --coverage --silent",
"test:watch": "jest --coverage --watch",
"test:debug": "node --inspect node_modules/.bin/jest --runInBand",
"test:open:integration": "cypress open",
"test:watch:integration": "concurrently --raw npm:dev 'npm:test:open:integration'",
"test:es-check": "npm run test:es-check:es2017 && npm run test:es-check:es2017:module",
"test:es-check:es2017": "es-check es2017 'dist/auth0-spa-js.production.js'",
"test:es-check:es2017:module": "es-check es2017 'dist/auth0-spa-js.production.esm.js' --module ",
"test:integration:server": "npm run dev",
"test:integration:tests": "wait-on http://localhost:3000/ && cypress run",
"test:integration": "concurrently --raw --kill-others --success first npm:test:integration:server npm:test:integration:tests",
"serve:coverage": "serve coverage/lcov-report -n",
"serve:stats": "serve bundle-stats -n",
"print-bundle-size": "node ./scripts/print-bundle-size.mjs",
"prepack": "npm run build && node ./scripts/prepack",
"publish:cdn": "ccu --trace"
},
"devDependencies": {
"@auth0/component-cdn-uploader": "^2.4.0",
"@rollup/plugin-replace": "^4.0.0",
"@types/cypress": "^1.1.3",
"@types/jest": "^28.1.7",
"@typescript-eslint/eslint-plugin-tslint": "^5.33.1",
"@typescript-eslint/parser": "^5.33.1",
"browserstack-cypress-cli": "1.32.8",
"cli-table": "^0.3.6",
"concurrently": "^7.3.0",
"cypress": "13.17.0",
"es-check": "^7.0.1",
"eslint": "^8.22.0",
"eslint-plugin-security": "^1.5.0",
"fake-indexeddb": "^6.0.1",
"gzip-size": "^7.0.0",
"husky": "^7.0.4",
"idtoken-verifier": "^2.2.2",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"jest-fetch-mock": "^3.0.3",
"jest-junit": "^14.0.0",
"jest-localstorage-mock": "^2.4.22",
"jsonwebtoken": "^9.0.0",
"oidc-provider": "^7.14.0",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.2",
"rimraf": "^3.0.2",
"rollup": "^2.78.0",
"rollup-plugin-analyzer": "^4.0.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-dev": "^1.1.3",
"rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.36.0",
"rollup-plugin-visualizer": "^5.7.1",
"rollup-plugin-web-worker-loader": "^1.6.1",
"serve": "^14.0.1",
"ts-jest": "^28.0.8",
"tslib": "^2.4.0",
"typedoc": "^0.25.1",
"typescript": "^4.7.4",
"wait-on": "^7.2.0"
},
"files": [
"src",
"dist"
],
"repository": {
"type": "git",
"url": "git://github.com/auth0/auth0-spa-js.git"
},
"bugs": {
"url": "https://github.com/auth0/auth0-spa-js/issues"
},
"homepage": "https://github.com/auth0/auth0-spa-js#readme",
"keywords": [
"auth0",
"login",
"Authorization Code Grant Flow",
"PKCE",
"Single Page Application authentication",
"SPA authentication"
],
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
}
}
File diff suppressed because it is too large. Load diff
+187
View File
@@ -0,0 +1,187 @@
import { ICache, InMemoryCache, LocalStorageCache } from './cache';
import {
Auth0ClientOptions,
AuthorizationParams,
AuthorizeOptions,
ClientAuthorizationParams,
LogoutOptions
} from './global';
import { scopesToRequest } from './scope';
/**
* @ignore
*/
export const GET_TOKEN_SILENTLY_LOCK_KEY = 'auth0.lock.getTokenSilently';
/**
* @ignore
*/
export const buildGetTokenSilentlyLockKey = (
clientId: string,
audience: string
) => `${GET_TOKEN_SILENTLY_LOCK_KEY}.${clientId}.${audience}`;
/**
* @ignore
*/
export const buildOrganizationHintCookieName = (clientId: string) =>
`auth0.${clientId}.organization_hint`;
/**
* @ignore
*/
export const OLD_IS_AUTHENTICATED_COOKIE_NAME = 'auth0.is.authenticated';
/**
* @ignore
*/
export const buildIsAuthenticatedCookieName = (clientId: string) =>
`auth0.${clientId}.is.authenticated`;
/**
* @ignore
*/
const cacheLocationBuilders: Record<string, () => ICache> = {
memory: () => new InMemoryCache().enclosedCache,
localstorage: () => new LocalStorageCache()
};
/**
* @ignore
*/
export const cacheFactory = (location: string) => {
return cacheLocationBuilders[location];
};
/**
* @ignore
*/
export const getAuthorizeParams = (
clientOptions: Auth0ClientOptions & {
authorizationParams: ClientAuthorizationParams;
},
scope: Record<string, string>,
authorizationParams: AuthorizationParams & { scope?: string },
state: string,
nonce: string,
code_challenge: string,
redirect_uri: string | undefined,
response_mode: string | undefined,
thumbprint: string | undefined
): AuthorizeOptions => {
return {
client_id: clientOptions.clientId,
...clientOptions.authorizationParams,
...authorizationParams,
scope: scopesToRequest(scope, authorizationParams.scope, authorizationParams.audience),
response_type: 'code',
response_mode: response_mode || 'query',
state,
nonce,
redirect_uri:
redirect_uri || clientOptions.authorizationParams.redirect_uri,
code_challenge,
code_challenge_method: 'S256',
dpop_jkt: thumbprint
};
};
/**
* @ignore
*
* Function used to provide support for the deprecated onRedirect through openUrl.
*/
export const patchOpenUrlWithOnRedirect = <
T extends Pick<LogoutOptions, 'openUrl' | 'onRedirect'>
>(
options: T
) => {
const { openUrl, onRedirect, ...originalOptions } = options;
const result = {
...originalOptions,
openUrl: openUrl === false || openUrl ? openUrl : onRedirect
};
return result as T;
};
/**
* @ignore
*
* Checks if all scopes are included inside other array of scopes
*/
export const allScopesAreIncluded = (scopeToInclude?: string, scopes?: string): boolean => {
const scopeGroup = scopes?.split(" ") || [];
const scopesToInclude = scopeToInclude?.split(" ") || [];
return scopesToInclude.every((key) => scopeGroup.includes(key));
}
/**
* @ignore
*
* Returns the scopes that are missing after a refresh
*/
export const getMissingScopes = (requestedScope?: string, respondedScope?: string): string => {
const requestedScopes = requestedScope?.split(" ") || [];
const respondedScopes = respondedScope?.split(" ") || [];
const missingScopes = requestedScopes.filter((scope) => respondedScopes.indexOf(scope) == -1);
return missingScopes.join(",");
}
/**
* @ignore
*
* For backward compatibility we are going to check if we are going to downscope while doing a refresh request
* while MRRT is allowed. If the audience is the same for the refresh_token we are going to use and it has
* lower scopes than the ones originally in the token, we are going to return the scopes that were stored
* with the refresh_token in the tokenset.
* @param useMrrt Setting that the user can activate to use MRRT in their requests
* @param authorizationParams Contains the audience and scope that the user requested to obtain a token
* @param cachedAudience Audience stored with the refresh_token wich we are going to use in the request
* @param cachedScope Scope stored with the refresh_token wich we are going to use in the request
*/
export const getScopeToRequest = (
useMrrt: boolean | undefined,
authorizationParams: { audience?: string, scope: string },
cachedAudience?: string,
cachedScope?: string
): string => {
if (useMrrt && cachedAudience && cachedScope) {
if (authorizationParams.audience !== cachedAudience) {
return authorizationParams.scope;
}
const cachedScopes = cachedScope.split(" ");
const newScopes = authorizationParams.scope?.split(" ") || [];
const newScopesAreIncluded = newScopes.every((scope) => cachedScopes.includes(scope));
return cachedScopes.length >= newScopes.length && newScopesAreIncluded ? cachedScope : authorizationParams.scope;
}
return authorizationParams.scope;
}
/**
* @ignore
*
* Checks if the refresh request has been done using MRRT
* @param cachedAudience Audience from the refresh token used to refresh
* @param cachedScope Scopes from the refresh token used to refresh
* @param requestAudience Audience sent to the server
* @param requestScope Scopes sent to the server
*/
export const isRefreshWithMrrt = (
cachedAudience: string | undefined,
cachedScope: string | undefined,
requestAudience: string | undefined,
requestScope: string,
): boolean => {
if (cachedAudience !== requestAudience) {
return true;
}
return !allScopesAreIncluded(requestScope, cachedScope);
}
+158
View File
@@ -0,0 +1,158 @@
import { AuthorizationParams } from './global';
import { Fetcher } from './fetcher';
interface ConnectRequest {
/** The name of the connection to link the account with (e.g., 'google-oauth2', 'facebook'). */
connection: string;
/** Array of scopes to request from the Identity Provider during the connect account flow. */
scopes?: string[];
/** The URI to redirect to after the connection process completes. */
redirect_uri: string;
/** An opaque value used to maintain state between the request and callback. */
state?: string;
/** The PKCE code challenge derived from the code verifier. */
code_challenge?: string;
/** The method used to derive the code challenge. Required when code_challenge is provided. */
code_challenge_method?: 'S256';
authorization_params?: AuthorizationParams;
}
interface ConnectResponse {
/** The base URI to initiate the account connection flow. */
connect_uri: string;
/** The authentication session identifier. */
auth_session: string;
/** Parameters to be used with the connect URI. */
connect_params: {
/** The ticket identifier to be used with the connection URI. */
ticket: string;
};
/** The number of seconds until the ticket expires. */
expires_in: number;
}
interface CompleteRequest {
/** The authentication session identifier */
auth_session: string;
/** The authorization code returned from the connect flow */
connect_code: string;
/** The redirect URI used in the original request */
redirect_uri: string;
/** The PKCE code verifier */
code_verifier?: string;
}
export interface CompleteResponse {
/** The unique identifier of the connected account */
id: string;
/** The connection name */
connection: string;
/** The access type, always 'offline' */
access_type: 'offline';
/** Array of scopes granted */
scopes?: string[];
/** ISO date string of when the connected account was created */
created_at: string;
/** ISO date string of when the refresh token expires (optional) */
expires_at?: string;
}
// Validation error returned from MyAccount API
export interface ErrorResponse {
type: string;
status: number;
title: string;
detail: string;
validation_errors?: {
detail: string;
field?: string;
pointer?: string;
source?: string;
}[];
}
/**
* Subset of the MyAccount API that handles the connect accounts flow.
*/
export class MyAccountApiClient {
constructor(
private myAccountFetcher: Fetcher<Response>,
private apiBase: string
) {}
/**
* Get a ticket for the connect account flow.
*/
async connectAccount(params: ConnectRequest): Promise<ConnectResponse> {
const res = await this.myAccountFetcher.fetchWithAuth(
`${this.apiBase}v1/connected-accounts/connect`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
}
);
return this._handleResponse(res);
}
/**
* Verify the redirect from the connect account flow and complete the connecting of the account.
*/
async completeAccount(params: CompleteRequest): Promise<CompleteResponse> {
const res = await this.myAccountFetcher.fetchWithAuth(
`${this.apiBase}v1/connected-accounts/complete`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params)
}
);
return this._handleResponse(res);
}
private async _handleResponse(res: Response) {
let body: any;
try {
body = await res.text();
body = JSON.parse(body);
} catch (err) {
throw new MyAccountApiError({
type: 'invalid_json',
status: res.status,
title: 'Invalid JSON response',
detail: body || String(err)
});
}
if (res.ok) {
return body;
} else {
throw new MyAccountApiError(body);
}
}
}
export class MyAccountApiError extends Error {
public readonly type: string;
public readonly status: number;
public readonly title: string;
public readonly detail: string;
public readonly validation_errors?: ErrorResponse['validation_errors'];
constructor({
type,
status,
title,
detail,
validation_errors
}: ErrorResponse) {
super(detail);
this.name = 'MyAccountApiError';
this.type = type;
this.status = status;
this.title = title;
this.detail = detail;
this.validation_errors = validation_errors;
Object.setPrototypeOf(this, MyAccountApiError.prototype);
}
}
+75
View File
@@ -0,0 +1,75 @@
/**
* Represents the configuration options required for initiating a Custom Token Exchange request
* following RFC 8693 specifications.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc8693 | RFC 8693: OAuth 2.0 Token Exchange}
*/
export type CustomTokenExchangeOptions = {
/**
* The type identifier for the subject token being exchanged
*
* @pattern
* - Must be a namespaced URI under your organization's control
* - Forbidden patterns:
* - `^urn:ietf:params:oauth:*` (IETF reserved)
* - `^https:\/\/auth0\.com/*` (Auth0 reserved)
* - `^urn:auth0:*` (Auth0 reserved)
*
* @example
* "urn:acme:legacy-system-token"
* "https://api.yourcompany.com/token-type/v1"
*/
subject_token_type: string;
/**
* The opaque token value being exchanged for Auth0 tokens
*
* @security
* - Must be validated in Auth0 Actions using strong cryptographic verification
* - Implement replay attack protection
* - Recommended validation libraries: `jose`, `jsonwebtoken`
*
* @example
* "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
*/
subject_token: string;
/**
* The target audience for the requested Auth0 token
*
* @remarks
* Must match exactly with an API identifier configured in your Auth0 tenant.
* If not provided, falls back to the client's default audience.
*
* @example
* "https://api.your-service.com/v1"
*/
audience?: string;
/**
* Space-separated list of OAuth 2.0 scopes being requested
*
* @remarks
* Subject to API authorization policies configured in Auth0
*
* @example
* "openid profile email read:data write:data"
*/
scope?: string;
/**
* Additional custom parameters for Auth0 Action processing
*
* @remarks
* Accessible in Action code via `event.request.body`
*
* @example
* ```typescript
* {
* custom_parameter: "session_context",
* device_fingerprint: "a3d8f7...",
* }
* ```
*/
[key: string]: unknown;
};
+61
View File
@@ -0,0 +1,61 @@
import { TokenEndpointOptions, TokenEndpointResponse } from './global';
import { DEFAULT_AUTH0_CLIENT, DEFAULT_AUDIENCE } from './constants';
import * as dpopUtils from './dpop/utils';
import { getJSON } from './http';
import { createQueryParams, stripAuth0Client } from './utils';
export async function oauthToken(
{
baseUrl,
timeout,
audience,
scope,
auth0Client,
useFormData,
useMrrt,
dpop,
...options
}: TokenEndpointOptions,
worker?: Worker
) {
const isTokenExchange =
options.grant_type === 'urn:ietf:params:oauth:grant-type:token-exchange';
const refreshWithMrrt = options.grant_type === 'refresh_token' && useMrrt;
const allParams = {
...options,
...(isTokenExchange && audience && { audience }),
...(isTokenExchange && scope && { scope }),
...(refreshWithMrrt && { audience, scope })
};
const body = useFormData
? createQueryParams(allParams)
: JSON.stringify(allParams);
const isDpopSupported = dpopUtils.isGrantTypeSupported(options.grant_type);
return await getJSON<TokenEndpointResponse>(
`${baseUrl}/oauth/token`,
timeout,
audience || DEFAULT_AUDIENCE,
scope,
{
method: 'POST',
body,
headers: {
'Content-Type': useFormData
? 'application/x-www-form-urlencoded'
: 'application/json',
'Auth0-Client': btoa(
JSON.stringify(stripAuth0Client(auth0Client || DEFAULT_AUTH0_CLIENT))
)
}
},
worker,
useFormData,
useMrrt,
isDpopSupported ? dpop : undefined
);
}
+31
View File
@@ -0,0 +1,31 @@
import { ICache, Cacheable, CACHE_KEY_PREFIX, MaybePromise } from './shared';
export class LocalStorageCache implements ICache {
public set<T = Cacheable>(key: string, entry: T) {
localStorage.setItem(key, JSON.stringify(entry));
}
public get<T = Cacheable>(key: string): MaybePromise<T | undefined> {
const json = window.localStorage.getItem(key);
if (!json) return;
try {
const payload = JSON.parse(json) as T;
return payload;
/* c8 ignore next 3 */
} catch (e) {
return;
}
}
public remove(key: string) {
localStorage.removeItem(key);
}
public allKeys() {
return Object.keys(window.localStorage).filter(key =>
key.startsWith(CACHE_KEY_PREFIX)
);
}
}
+299
View File
@@ -0,0 +1,299 @@
import { DEFAULT_NOW_PROVIDER } from '../constants';
import { CacheKeyManifest } from './key-manifest';
import {
CacheEntry,
ICache,
CacheKey,
CACHE_KEY_PREFIX,
WrappedCacheEntry,
DecodedToken,
CACHE_KEY_ID_TOKEN_SUFFIX,
IdTokenEntry
} from './shared';
const DEFAULT_EXPIRY_ADJUSTMENT_SECONDS = 0;
export class CacheManager {
private nowProvider: () => number | Promise<number>;
constructor(
private cache: ICache,
private keyManifest?: CacheKeyManifest,
nowProvider?: () => number | Promise<number>
) {
this.nowProvider = nowProvider || DEFAULT_NOW_PROVIDER;
}
async setIdToken(
clientId: string,
idToken: string,
decodedToken: DecodedToken
): Promise<void> {
const cacheKey = this.getIdTokenCacheKey(clientId);
await this.cache.set(cacheKey, {
id_token: idToken,
decodedToken
});
await this.keyManifest?.add(cacheKey);
}
async getIdToken(cacheKey: CacheKey): Promise<IdTokenEntry | undefined> {
const entry = await this.cache.get<IdTokenEntry>(
this.getIdTokenCacheKey(cacheKey.clientId)
);
if (!entry && cacheKey.scope && cacheKey.audience) {
const entryByScope = await this.get(cacheKey);
if (!entryByScope) {
return;
}
if (!entryByScope.id_token || !entryByScope.decodedToken) {
return;
}
return {
id_token: entryByScope.id_token,
decodedToken: entryByScope.decodedToken
};
}
if (!entry) {
return;
}
return { id_token: entry.id_token, decodedToken: entry.decodedToken };
}
async get(
cacheKey: CacheKey,
expiryAdjustmentSeconds = DEFAULT_EXPIRY_ADJUSTMENT_SECONDS,
useMrrt = false,
cacheMode?: string
): Promise<Partial<CacheEntry> | undefined> {
let wrappedEntry = await this.cache.get<WrappedCacheEntry>(
cacheKey.toKey()
);
if (!wrappedEntry) {
const keys = await this.getCacheKeys();
if (!keys) return;
const matchedKey = this.matchExistingCacheKey(cacheKey, keys);
if (matchedKey) {
wrappedEntry = await this.cache.get<WrappedCacheEntry>(matchedKey);
}
// To refresh using MRRT we need to send a request to the server
// If cacheMode is 'cache-only', this will make us unable to call the server
// so it won't be needed to find a valid refresh token
if (!matchedKey && useMrrt && cacheMode !== 'cache-only') {
return this.getEntryWithRefreshToken(cacheKey, keys);
}
}
// If we still don't have an entry, exit.
if (!wrappedEntry) {
return;
}
const now = await this.nowProvider();
const nowSeconds = Math.floor(now / 1000);
if (wrappedEntry.expiresAt - expiryAdjustmentSeconds < nowSeconds) {
if (wrappedEntry.body.refresh_token) {
return this.modifiedCachedEntry(wrappedEntry, cacheKey);
}
await this.cache.remove(cacheKey.toKey());
await this.keyManifest?.remove(cacheKey.toKey());
return;
}
return wrappedEntry.body;
}
private async modifiedCachedEntry(wrappedEntry: WrappedCacheEntry, cacheKey: CacheKey): Promise<Partial<CacheEntry>> {
// We need to keep audience and scope in order to check them later when doing refresh
// using MRRT. See getScopeToRequest method.
wrappedEntry.body = {
refresh_token: wrappedEntry.body.refresh_token,
audience: wrappedEntry.body.audience,
scope: wrappedEntry.body.scope,
};
await this.cache.set(cacheKey.toKey(), wrappedEntry);
return {
refresh_token: wrappedEntry.body.refresh_token,
audience: wrappedEntry.body.audience,
scope: wrappedEntry.body.scope,
};
}
async set(entry: CacheEntry): Promise<void> {
const cacheKey = new CacheKey({
clientId: entry.client_id,
scope: entry.scope,
audience: entry.audience
});
const wrappedEntry = await this.wrapCacheEntry(entry);
await this.cache.set(cacheKey.toKey(), wrappedEntry);
await this.keyManifest?.add(cacheKey.toKey());
}
async remove(
client_id: string,
audience?: string,
scope?: string,
): Promise<void> {
const cacheKey = new CacheKey({
clientId: client_id,
scope: scope,
audience: audience
});
await this.cache.remove(cacheKey.toKey());
}
async clear(clientId?: string): Promise<void> {
const keys = await this.getCacheKeys();
/* c8 ignore next */
if (!keys) return;
await keys
.filter(key => (clientId ? key.includes(clientId) : true))
.reduce(async (memo, key) => {
await memo;
await this.cache.remove(key);
}, Promise.resolve());
await this.keyManifest?.clear();
}
private async wrapCacheEntry(entry: CacheEntry): Promise<WrappedCacheEntry> {
const now = await this.nowProvider();
const expiresInTime = Math.floor(now / 1000) + entry.expires_in;
return {
body: entry,
expiresAt: expiresInTime
};
}
private async getCacheKeys(): Promise<string[] | undefined> {
if (this.keyManifest) {
return (await this.keyManifest.get())?.keys;
} else if (this.cache.allKeys) {
return this.cache.allKeys();
}
}
/**
* Returns the cache key to be used to store the id token
* @param clientId The client id used to link to the id token
* @returns The constructed cache key, as a string, to store the id token
*/
private getIdTokenCacheKey(clientId: string) {
return new CacheKey(
{ clientId },
CACHE_KEY_PREFIX,
CACHE_KEY_ID_TOKEN_SUFFIX
).toKey();
}
/**
* Finds the corresponding key in the cache based on the provided cache key.
* The keys inside the cache are in the format {prefix}::{clientId}::{audience}::{scope}.
* The first key in the cache that satisfies the following conditions is returned
* - `prefix` is strict equal to Auth0's internally configured `keyPrefix`
* - `clientId` is strict equal to the `cacheKey.clientId`
* - `audience` is strict equal to the `cacheKey.audience`
* - `scope` contains at least all the `cacheKey.scope` values
* *
* @param keyToMatch The provided cache key
* @param allKeys A list of existing cache keys
*/
private matchExistingCacheKey(keyToMatch: CacheKey, allKeys: Array<string>) {
return allKeys.filter(key => {
const cacheKey = CacheKey.fromKey(key);
const scopeSet = new Set(cacheKey.scope && cacheKey.scope.split(' '));
const scopesToMatch = keyToMatch.scope?.split(' ') || [];
const hasAllScopes =
cacheKey.scope &&
scopesToMatch.reduce(
(acc, current) => acc && scopeSet.has(current),
true
);
return (
cacheKey.prefix === CACHE_KEY_PREFIX &&
cacheKey.clientId === keyToMatch.clientId &&
cacheKey.audience === keyToMatch.audience &&
hasAllScopes
);
})[0];
}
/**
* Returns the first entry that contains a refresh_token that satisfies the following conditions
* The keys inside the cache are in the format {prefix}::{clientId}::{audience}::{scope}.
* - `prefix` is strict equal to Auth0's internally configured `keyPrefix`
* - `clientId` is strict equal to the `cacheKey.clientId`
* @param keyToMatch The provided cache key
* @param allKeys A list of existing cache keys
*/
private async getEntryWithRefreshToken(keyToMatch: CacheKey, allKeys: Array<string>): Promise<Partial<CacheEntry> | undefined> {
for (const key of allKeys) {
const cacheKey = CacheKey.fromKey(key);
if (cacheKey.prefix === CACHE_KEY_PREFIX &&
cacheKey.clientId === keyToMatch.clientId) {
const cachedEntry = await this.cache.get<WrappedCacheEntry>(key);
if (cachedEntry?.body?.refresh_token) {
return this.modifiedCachedEntry(cachedEntry, keyToMatch);
}
}
}
return undefined;
}
/**
* Updates in the cache all entries that has a match with previous refresh_token with the
* new refresh_token obtained from the server
* @param oldRefreshToken Old refresh_token used on refresh
* @param newRefreshToken New refresh_token obtained from the server after refresh
*/
async updateEntry(
oldRefreshToken: string,
newRefreshToken: string,
): Promise<void> {
const allKeys = await this.getCacheKeys();
if (!allKeys) return;
for (const key of allKeys) {
const entry = await this.cache.get<WrappedCacheEntry>(key);
if (entry?.body?.refresh_token === oldRefreshToken) {
const cacheEntry = {
...entry.body,
refresh_token: newRefreshToken,
} as CacheEntry;
await this.set(cacheEntry);
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
import { Cacheable, ICache, MaybePromise } from './shared';
export class InMemoryCache {
public enclosedCache: ICache = (function () {
let cache: Record<string, unknown> = {};
return {
set<T = Cacheable>(key: string, entry: T) {
cache[key] = entry;
},
get<T = Cacheable>(key: string): MaybePromise<T | undefined> {
const cacheEntry = cache[key] as T;
if (!cacheEntry) {
return;
}
return cacheEntry;
},
remove(key: string) {
delete cache[key];
},
allKeys(): string[] {
return Object.keys(cache);
}
};
})();
}
+4
View File
@@ -0,0 +1,4 @@
export * from './cache-localstorage';
export * from './cache-memory';
export * from './cache-manager';
export * from './shared';
+53
View File
@@ -0,0 +1,53 @@
import {
CACHE_KEY_PREFIX,
ICache,
KeyManifestEntry,
MaybePromise
} from './shared';
export class CacheKeyManifest {
private readonly manifestKey: string;
constructor(private cache: ICache, private clientId: string) {
this.manifestKey = this.createManifestKeyFrom(this.clientId);
}
async add(key: string): Promise<void> {
const keys = new Set(
(await this.cache.get<KeyManifestEntry>(this.manifestKey))?.keys || []
);
keys.add(key);
await this.cache.set<KeyManifestEntry>(this.manifestKey, {
keys: [...keys]
});
}
async remove(key: string): Promise<void> {
const entry = await this.cache.get<KeyManifestEntry>(this.manifestKey);
if (entry) {
const keys = new Set(entry.keys);
keys.delete(key);
if (keys.size > 0) {
return await this.cache.set(this.manifestKey, { keys: [...keys] });
}
return await this.cache.remove(this.manifestKey);
}
}
get(): MaybePromise<KeyManifestEntry | undefined> {
return this.cache.get<KeyManifestEntry>(this.manifestKey);
}
clear(): MaybePromise<void> {
return this.cache.remove(this.manifestKey);
}
private createManifestKeyFrom(clientId: string): string {
return `${CACHE_KEY_PREFIX}::${clientId}`;
}
}
+105
View File
@@ -0,0 +1,105 @@
import { IdToken, User } from '../global';
export const CACHE_KEY_PREFIX = '@@auth0spajs@@';
export const CACHE_KEY_ID_TOKEN_SUFFIX = '@@user@@';
export type CacheKeyData = {
audience?: string;
scope?: string;
clientId: string;
};
export class CacheKey {
public clientId: string;
public scope?: string;
public audience?: string;
constructor(
data: CacheKeyData,
public prefix: string = CACHE_KEY_PREFIX,
public suffix?: string
) {
this.clientId = data.clientId;
this.scope = data.scope;
this.audience = data.audience;
}
/**
* Converts this `CacheKey` instance into a string for use in a cache
* @returns A string representation of the key
*/
toKey(): string {
return [this.prefix, this.clientId, this.audience, this.scope, this.suffix]
.filter(Boolean)
.join('::');
}
/**
* Converts a cache key string into a `CacheKey` instance.
* @param key The key to convert
* @returns An instance of `CacheKey`
*/
static fromKey(key: string): CacheKey {
const [prefix, clientId, audience, scope] = key.split('::');
return new CacheKey({ clientId, scope, audience }, prefix);
}
/**
* Utility function to build a `CacheKey` instance from a cache entry
* @param entry The entry
* @returns An instance of `CacheKey`
*/
static fromCacheEntry(entry: CacheEntry): CacheKey {
const { scope, audience, client_id: clientId } = entry;
return new CacheKey({
scope,
audience,
clientId
});
}
}
export interface DecodedToken {
claims: IdToken;
user: User;
}
export interface IdTokenEntry {
id_token: string;
decodedToken: DecodedToken;
}
export type CacheEntry = {
id_token?: string;
token_type?: string;
access_token: string;
expires_in: number;
decodedToken?: DecodedToken;
audience: string;
scope: string;
client_id: string;
refresh_token?: string;
oauthTokenScope?: string;
};
export type WrappedCacheEntry = {
body: Partial<CacheEntry>;
expiresAt: number;
};
export type KeyManifestEntry = {
keys: string[];
};
export type Cacheable = WrappedCacheEntry | KeyManifestEntry;
export type MaybePromise<T> = Promise<T> | T;
export interface ICache {
set<T = Cacheable>(key: string, entry: T): MaybePromise<void>;
get<T = Cacheable>(key: string): MaybePromise<T | undefined>;
remove(key: string): MaybePromise<void>;
allKeys?(): MaybePromise<string[]>;
}
+64
View File
@@ -0,0 +1,64 @@
import { PopupConfigOptions } from './global';
import version from './version';
/**
* @ignore
*/
export const DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS = 60;
/**
* @ignore
*/
export const DEFAULT_POPUP_CONFIG_OPTIONS: PopupConfigOptions = {
timeoutInSeconds: DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS
};
/**
* @ignore
*/
export const DEFAULT_SILENT_TOKEN_RETRY_COUNT = 3;
/**
* @ignore
*/
export const CLEANUP_IFRAME_TIMEOUT_IN_SECONDS = 2;
/**
* @ignore
*/
export const DEFAULT_FETCH_TIMEOUT_MS = 10000;
export const CACHE_LOCATION_MEMORY = 'memory';
export const CACHE_LOCATION_LOCAL_STORAGE = 'localstorage';
/**
* @ignore
*/
export const MISSING_REFRESH_TOKEN_ERROR_MESSAGE = 'Missing Refresh Token';
/**
* @ignore
*/
export const INVALID_REFRESH_TOKEN_ERROR_MESSAGE = 'invalid refresh token';
/**
* @ignore
*/
export const DEFAULT_SCOPE = 'openid profile email';
/**
* @ignore
*/
export const DEFAULT_SESSION_CHECK_EXPIRY_DAYS = 1;
/**
* @ignore
*/
export const DEFAULT_AUTH0_CLIENT = {
name: 'auth0-spa-js',
version: version
};
export const DEFAULT_NOW_PROVIDER = () => Date.now();
export const DEFAULT_AUDIENCE = 'default';
+56
View File
@@ -0,0 +1,56 @@
import { DpopStorage } from './storage';
import * as dpopUtils from './utils';
export class Dpop {
protected readonly storage: DpopStorage;
public constructor(clientId: string) {
this.storage = new DpopStorage(clientId);
}
public getNonce(id?: string): Promise<string | undefined> {
return this.storage.findNonce(id);
}
public setNonce(nonce: string, id?: string): Promise<void> {
return this.storage.setNonce(nonce, id);
}
protected async getOrGenerateKeyPair(): Promise<dpopUtils.KeyPair> {
let keyPair = await this.storage.findKeyPair();
if (!keyPair) {
keyPair = await dpopUtils.generateKeyPair();
await this.storage.setKeyPair(keyPair);
}
return keyPair;
}
public async generateProof(params: {
url: string;
method: string;
nonce?: string;
accessToken?: string;
}): Promise<string> {
const keyPair = await this.getOrGenerateKeyPair();
return dpopUtils.generateProof({
keyPair,
...params
});
}
public async calculateThumbprint(): Promise<string> {
const keyPair = await this.getOrGenerateKeyPair();
return dpopUtils.calculateThumbprint(keyPair);
}
public async clear(): Promise<void> {
await Promise.all([
this.storage.clearNonces(),
this.storage.clearKeyPairs()
]);
}
}
+134
View File
@@ -0,0 +1,134 @@
import { type KeyPair } from './utils';
const VERSION = 1;
const NAME = 'auth0-spa-js';
const TABLES = {
NONCE: 'nonce',
KEYPAIR: 'keypair'
} as const;
const AUTH0_NONCE_ID = 'auth0';
type Table = (typeof TABLES)[keyof typeof TABLES];
export class DpopStorage {
protected readonly clientId: string;
protected dbHandle: IDBDatabase | undefined;
constructor(clientId: string) {
this.clientId = clientId;
}
protected getVersion(): number {
return VERSION;
}
protected createDbHandle(): Promise<IDBDatabase> {
const req = window.indexedDB.open(NAME, this.getVersion());
return new Promise((resolve, reject) => {
req.onupgradeneeded = () =>
Object.values(TABLES).forEach(t => req.result.createObjectStore(t));
req.onerror = () => reject(req.error);
req.onsuccess = () => resolve(req.result);
});
}
protected async getDbHandle(): Promise<IDBDatabase> {
if (!this.dbHandle) {
this.dbHandle = await this.createDbHandle();
}
return this.dbHandle;
}
protected async executeDbRequest<T = unknown>(
table: string,
mode: IDBTransactionMode,
requestFactory: (table: IDBObjectStore) => IDBRequest<T>
): Promise<T> {
const db = await this.getDbHandle();
const txn = db.transaction(table, mode);
const store = txn.objectStore(table);
const request = requestFactory(store);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
protected buildKey(id?: string): string {
const finalId = id
? `_${id}` // prefix to avoid collisions
: AUTH0_NONCE_ID;
return `${this.clientId}::${finalId}`;
}
public setNonce(nonce: string, id?: string): Promise<void> {
return this.save(TABLES.NONCE, this.buildKey(id), nonce);
}
public setKeyPair(keyPair: KeyPair): Promise<void> {
return this.save(TABLES.KEYPAIR, this.buildKey(), keyPair);
}
protected async save(
table: Table,
key: IDBValidKey,
obj: unknown
): Promise<void> {
return void await this.executeDbRequest(table, 'readwrite', table =>
table.put(obj, key)
);
}
public findNonce(id?: string): Promise<string | undefined> {
return this.find(TABLES.NONCE, this.buildKey(id));
}
public findKeyPair(): Promise<KeyPair | undefined> {
return this.find(TABLES.KEYPAIR, this.buildKey());
}
protected find<T = unknown>(
table: Table,
key: IDBValidKey
): Promise<T | undefined> {
return this.executeDbRequest(table, 'readonly', table => table.get(key));
}
protected async deleteBy(
table: Table,
predicate: (key: IDBValidKey) => boolean
): Promise<void> {
const allKeys = await this.executeDbRequest(table, 'readonly', table =>
table.getAllKeys()
);
allKeys
?.filter(predicate)
.map(k =>
this.executeDbRequest(table, 'readwrite', table => table.delete(k))
);
}
protected deleteByClientId(table: Table, clientId: string): Promise<void> {
return this.deleteBy(
table,
k => typeof k === 'string' && k.startsWith(`${clientId}::`)
);
}
public clearNonces(): Promise<void> {
return this.deleteByClientId(TABLES.NONCE, this.clientId);
}
public clearKeyPairs(): Promise<void> {
return this.deleteByClientId(TABLES.KEYPAIR, this.clientId);
}
}
+66
View File
@@ -0,0 +1,66 @@
import * as dpopLib from 'dpop';
export const DPOP_NONCE_HEADER = 'dpop-nonce';
const KEY_PAIR_ALGORITHM: dpopLib.JWSAlgorithm = 'ES256';
const SUPPORTED_GRANT_TYPES = [
'authorization_code',
'refresh_token',
'urn:ietf:params:oauth:grant-type:token-exchange'
];
export type KeyPair = Readonly<dpopLib.KeyPair>;
type GenerateProofParams = {
keyPair: KeyPair;
url: string;
method: string;
nonce?: string;
accessToken?: string;
};
export function generateKeyPair(): Promise<KeyPair> {
return dpopLib.generateKeyPair(KEY_PAIR_ALGORITHM, { extractable: false });
}
export function calculateThumbprint(
keyPair: Pick<KeyPair, 'publicKey'>
): Promise<string> {
return dpopLib.calculateThumbprint(keyPair.publicKey);
}
function normalizeUrl(url: string): string {
const parsedUrl = new URL(url);
/**
* "The HTTP target URI (...) without query and fragment parts"
* @see {@link https://www.rfc-editor.org/rfc/rfc9449.html#section-4.2-4.6}
*/
parsedUrl.search = '';
parsedUrl.hash = '';
return parsedUrl.href;
}
export function generateProof({
keyPair,
url,
method,
nonce,
accessToken
}: GenerateProofParams): Promise<string> {
const normalizedUrl = normalizeUrl(url);
return dpopLib.generateProof(
keyPair,
normalizedUrl,
method,
nonce,
accessToken
);
}
export function isGrantTypeSupported(grantType: string): boolean {
return SUPPORTED_GRANT_TYPES.includes(grantType);
}
+151
View File
@@ -0,0 +1,151 @@
/**
* Thrown when network requests to the Auth server fail.
*/
export class GenericError extends Error {
constructor(public error: string, public error_description: string) {
super(error_description);
Object.setPrototypeOf(this, GenericError.prototype);
}
static fromPayload({
error,
error_description
}: {
error: string;
error_description: string;
}) {
return new GenericError(error, error_description);
}
}
/**
* Thrown when handling the redirect callback fails, will be one of Auth0's
* Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses
*/
export class AuthenticationError extends GenericError {
constructor(
error: string,
error_description: string,
public state: string,
public appState: any = null
) {
super(error, error_description);
//https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, AuthenticationError.prototype);
}
}
/**
* Thrown when handling the redirect callback for the connect flow fails, will be one of Auth0's
* Authentication API's Standard Error Responses: https://auth0.com/docs/api/authentication?javascript#standard-error-responses
*/
export class ConnectError extends GenericError {
constructor(
error: string,
error_description: string,
public connection: string,
public state: string,
public appState: any = null
) {
super(error, error_description);
//https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, ConnectError.prototype);
}
}
/**
* Thrown when silent auth times out (usually due to a configuration issue) or
* when network requests to the Auth server timeout.
*/
export class TimeoutError extends GenericError {
constructor() {
super('timeout', 'Timeout');
//https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, TimeoutError.prototype);
}
}
/**
* Error thrown when the login popup times out (if the user does not complete auth)
*/
export class PopupTimeoutError extends TimeoutError {
constructor(public popup: Window) {
super();
//https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, PopupTimeoutError.prototype);
}
}
export class PopupCancelledError extends GenericError {
constructor(public popup: Window) {
super('cancelled', 'Popup closed');
//https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, PopupCancelledError.prototype);
}
}
/**
* Error thrown when the token exchange results in a `mfa_required` error
*/
export class MfaRequiredError extends GenericError {
constructor(
error: string,
error_description: string,
public mfa_token: string
) {
super(error, error_description);
//https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, MfaRequiredError.prototype);
}
}
/**
* Error thrown when there is no refresh token to use
*/
export class MissingRefreshTokenError extends GenericError {
constructor(public audience: string, public scope: string) {
super(
'missing_refresh_token',
`Missing Refresh Token (audience: '${valueOrEmptyString(audience, [
'default'
])}', scope: '${valueOrEmptyString(scope)}')`
);
Object.setPrototypeOf(this, MissingRefreshTokenError.prototype);
}
}
/**
* Error thrown when there are missing scopes after refreshing a token
*/
export class MissingScopesError extends GenericError {
constructor(public audience: string, public scope: string) {
super(
'missing_scopes',
`Missing requested scopes after refresh (audience: '${valueOrEmptyString(audience, [
'default'
])}', missing scope: '${valueOrEmptyString(scope)}')`
);
Object.setPrototypeOf(this, MissingScopesError.prototype);
}
}
/**
* Error thrown when the wrong DPoP nonce is used and a potential subsequent retry wasn't able to fix it.
*/
export class UseDpopNonceError extends GenericError {
constructor(public newDpopNonce: string | undefined) {
super('use_dpop_nonce', 'Server rejected DPoP proof: wrong nonce');
Object.setPrototypeOf(this, UseDpopNonceError.prototype);
}
}
/**
* Returns an empty string when value is falsy, or when it's value is included in the exclude argument.
* @param value The value to check
* @param exclude An array of values that should result in an empty string.
* @returns The value, or an empty string when falsy or included in the exclude argument.
*/
function valueOrEmptyString(value: string, exclude: string[] = []) {
return value && !exclude.includes(value) ? value : '';
}
+271
View File
@@ -0,0 +1,271 @@
import { DPOP_NONCE_HEADER } from './dpop/utils';
import { UseDpopNonceError } from './errors';
import { GetTokenSilentlyVerboseResponse } from './global';
export type ResponseHeaders =
| Record<string, string | null | undefined>
| [string, string][]
| { get(name: string): string | null | undefined };
export type CustomFetchMinimalOutput = {
status: number;
headers: ResponseHeaders;
};
export type CustomFetchImpl<TOutput extends CustomFetchMinimalOutput> = (
req: Request
) => Promise<TOutput>;
export type AuthParams = {
scope?: string[];
audience?: string;
};
type AccessTokenFactory = (authParams?: AuthParams) => Promise<string | GetTokenSilentlyVerboseResponse>;
enum TokenType {
Bearer = 'Bearer',
DPoP = 'DPoP'
}
export type FetcherConfig<TOutput extends CustomFetchMinimalOutput> = {
getAccessToken?: AccessTokenFactory;
baseUrl?: string;
fetch?: CustomFetchImpl<TOutput>;
dpopNonceId?: string;
};
export type FetcherHooks = {
isDpopEnabled: () => boolean;
getAccessToken: AccessTokenFactory;
getDpopNonce: () => Promise<string | undefined>;
setDpopNonce: (nonce: string) => Promise<void>;
generateDpopProof: (params: {
url: string;
method: string;
nonce?: string;
accessToken: string;
}) => Promise<string>;
};
export type FetchWithAuthCallbacks<TOutput> = {
onUseDpopNonceError?(): Promise<TOutput>;
};
export class Fetcher<TOutput extends CustomFetchMinimalOutput> {
protected readonly config: Omit<FetcherConfig<TOutput>, 'fetch'> &
Required<Pick<FetcherConfig<TOutput>, 'fetch'>>;
protected readonly hooks: FetcherHooks;
constructor(config: FetcherConfig<TOutput>, hooks: FetcherHooks) {
this.hooks = hooks;
this.config = {
...config,
fetch:
config.fetch ||
// For easier testing and constructor compatibility with SSR.
((typeof window === 'undefined'
? fetch
: window.fetch.bind(window)) as () => Promise<any>)
};
}
protected isAbsoluteUrl(url: string): boolean {
// `http://example.com`, `https://example.com` or `//example.com`
return /^(https?:)?\/\//i.test(url);
}
protected buildUrl(
baseUrl: string | undefined,
url: string | undefined
): string {
if (url) {
if (this.isAbsoluteUrl(url)) {
return url;
}
if (baseUrl) {
return `${baseUrl.replace(/\/?\/$/, '')}/${url.replace(/^\/+/, '')}`;
}
}
throw new TypeError('`url` must be absolute or `baseUrl` non-empty.');
}
protected getAccessToken(authParams?: AuthParams): Promise<string | GetTokenSilentlyVerboseResponse> {
return this.config.getAccessToken
? this.config.getAccessToken(authParams)
: this.hooks.getAccessToken(authParams);
}
protected extractUrl(info: RequestInfo | URL): string {
if (typeof info === 'string') {
return info;
}
if (info instanceof URL) {
return info.href;
}
return info.url;
}
protected buildBaseRequest(
info: RequestInfo | URL,
init: RequestInit | undefined
): Request {
// In the native `fetch()` behavior, `init` can override `info` and the result
// is the merge of both. So let's replicate that behavior by passing those into
// a fresh `Request` object.
// No `baseUrl`? We can use `info` and `init` as is.
if (!this.config.baseUrl) {
return new Request(info, init);
}
// But if `baseUrl` is present, first we have to build the final URL...
const finalUrl = this.buildUrl(this.config.baseUrl, this.extractUrl(info));
// ... and then overwrite `info`'s URL with it, making sure we keep any other
// properties that might be there already (headers, etc).
const finalInfo = info instanceof Request
? new Request(finalUrl, info)
: finalUrl;
return new Request(finalInfo, init);
}
protected setAuthorizationHeader(
request: Request,
accessToken: string,
tokenType: string = TokenType.Bearer
): void {
request.headers.set(
'authorization',
`${tokenType} ${accessToken}`
);
}
protected async setDpopProofHeader(
request: Request,
accessToken: string
): Promise<void> {
if (!this.config.dpopNonceId) {
return;
}
const dpopNonce = await this.hooks.getDpopNonce();
const dpopProof = await this.hooks.generateDpopProof({
accessToken,
method: request.method,
nonce: dpopNonce,
url: request.url
});
request.headers.set('dpop', dpopProof);
}
protected async prepareRequest(request: Request, authParams?: AuthParams) {
const accessTokenResponse = await this.getAccessToken(authParams);
let tokenType: string;
let accessToken: string;
if (typeof accessTokenResponse === 'string') {
tokenType = this.config.dpopNonceId ? TokenType.DPoP : TokenType.Bearer;
accessToken = accessTokenResponse;
} else {
tokenType = accessTokenResponse.token_type;
accessToken = accessTokenResponse.access_token;
}
this.setAuthorizationHeader(request, accessToken, tokenType);
if (tokenType === TokenType.DPoP) {
await this.setDpopProofHeader(request, accessToken);
}
}
protected getHeader(headers: ResponseHeaders, name: string): string {
if (Array.isArray(headers)) {
return new Headers(headers).get(name) || '';
}
if (typeof headers.get === 'function') {
return headers.get(name) || '';
}
return (headers as Record<string, string | null | undefined>)[name] || '';
}
protected hasUseDpopNonceError(response: TOutput): boolean {
if (response.status !== 401) {
return false;
}
const wwwAuthHeader = this.getHeader(response.headers, 'www-authenticate');
return wwwAuthHeader.includes('invalid_dpop_nonce') || wwwAuthHeader.includes('use_dpop_nonce');
}
protected async handleResponse(
response: TOutput,
callbacks: FetchWithAuthCallbacks<TOutput>
): Promise<TOutput> {
const newDpopNonce = this.getHeader(response.headers, DPOP_NONCE_HEADER);
if (newDpopNonce) {
await this.hooks.setDpopNonce(newDpopNonce);
}
if (!this.hasUseDpopNonceError(response)) {
return response;
}
// After a `use_dpop_nonce` error, if we didn't get a new DPoP nonce or we
// did but it still got rejected for the same reason, we have to give up.
if (!newDpopNonce || !callbacks.onUseDpopNonceError) {
throw new UseDpopNonceError(newDpopNonce);
}
return callbacks.onUseDpopNonceError();
}
protected async internalFetchWithAuth(
info: RequestInfo | URL,
init: RequestInit | undefined,
callbacks: FetchWithAuthCallbacks<TOutput>,
authParams?: AuthParams
): Promise<TOutput> {
const request = this.buildBaseRequest(info, init);
await this.prepareRequest(request, authParams);
const response = await this.config.fetch(request);
return this.handleResponse(response, callbacks);
}
public fetchWithAuth(
info: RequestInfo | URL,
init?: RequestInit,
authParams?: AuthParams
): Promise<TOutput> {
const callbacks: FetchWithAuthCallbacks<TOutput> = {
onUseDpopNonceError: () =>
this.internalFetchWithAuth(
info,
init,
{
...callbacks,
// Retry on a `use_dpop_nonce` error, but just once.
onUseDpopNonceError: undefined
},
authParams
)
};
return this.internalFetchWithAuth(info, init, callbacks, authParams);
}
}
+793
View File
@@ -0,0 +1,793 @@
import { ICache } from './cache';
import type { Dpop } from './dpop/dpop';
import { CompleteResponse } from './MyAccountApiClient';
export interface AuthorizationParams {
/**
* - `'page'`: displays the UI with a full page view
* - `'popup'`: displays the UI with a popup window
* - `'touch'`: displays the UI in a way that leverages a touch interface
* - `'wap'`: displays the UI with a "feature phone" type interface
*/
display?: 'page' | 'popup' | 'touch' | 'wap';
/**
* - `'none'`: do not prompt user for login or consent on reauthentication
* - `'login'`: prompt user for reauthentication
* - `'consent'`: prompt user for consent before processing request
* - `'select_account'`: prompt user to select an account
*/
prompt?: 'none' | 'login' | 'consent' | 'select_account';
/**
* Maximum allowable elapsed time (in seconds) since authentication.
* If the last time the user authenticated is greater than this value,
* the user must be reauthenticated.
*/
max_age?: string | number;
/**
* The space-separated list of language tags, ordered by preference.
* For example: `'fr-CA fr en'`.
*/
ui_locales?: string;
/**
* Previously issued ID Token.
*/
id_token_hint?: string;
/**
* Provides a hint to Auth0 as to what flow should be displayed.
* The default behavior is to show a login page but you can override
* this by passing 'signup' to show the signup page instead.
*
* This only affects the New Universal Login Experience.
*/
screen_hint?: 'signup' | 'login' | string;
/**
* The user's email address or other identifier. When your app knows
* which user is trying to authenticate, you can provide this parameter
* to pre-fill the email box or select the right session for sign-in.
*
* This currently only affects the classic Lock experience.
*/
login_hint?: string;
acr_values?: string;
/**
* The default scope to be used on authentication requests.
*
* This defaults to `profile email` if not set. If you are setting extra scopes and require
* `profile` and `email` to be included then you must include them in the provided scope.
*
* Note: The `openid` scope is **always applied** regardless of this setting.
*/
scope?: string;
/**
* The default audience to be used for requesting API access.
*/
audience?: string;
/**
* The name of the connection configured for your application.
* If null, it will redirect to the Auth0 Login Page and show
* the Login Widget.
*/
connection?: string;
/**
* The organization to log in to.
*
* This will specify an `organization` parameter in your user's login request.
*
* - If you provide an Organization ID (a string with the prefix `org_`), it will be validated against the `org_id` claim of your user's ID Token. The validation is case-sensitive.
* - If you provide an Organization Name (a string *without* the prefix `org_`), it will be validated against the `org_name` claim of your user's ID Token. The validation is case-insensitive.
* To use an Organization Name you must have "Allow Organization Names in Authentication API" switched on in your Auth0 settings dashboard.
* More information is available on the [Auth0 documentation portal](https://auth0.com/docs/manage-users/organizations/configure-organizations/use-org-name-authentication-api)
*
*/
organization?: string;
/**
* The Id of an invitation to accept. This is available from the user invitation URL that is given when participating in a user invitation flow.
*/
invitation?: string;
/**
* The default URL where Auth0 will redirect your browser to with
* the authentication result. It must be whitelisted in
* the "Allowed Callback URLs" field in your Auth0 Application's
* settings. If not provided here, it should be provided in the other
* methods that provide authentication.
*/
redirect_uri?: string;
/**
* If you need to send custom parameters to the Authorization Server,
* make sure to use the original parameter name.
*/
[key: string]: any;
}
export interface ClientAuthorizationParams extends Omit<AuthorizationParams, 'scope'> {
scope?: string | Record<string, string>
};
interface BaseLoginOptions {
/**
* URL parameters that will be sent back to the Authorization Server. This can be known parameters
* defined by Auth0 or custom parameters that you define.
*/
authorizationParams?: AuthorizationParams;
}
export interface Auth0ClientOptions {
/**
* Your Auth0 account domain such as `'example.auth0.com'`,
* `'example.eu.auth0.com'` or , `'example.mycompany.com'`
* (when using [custom domains](https://auth0.com/docs/custom-domains))
*/
domain: string;
/**
* The issuer to be used for validation of JWTs, optionally defaults to the domain above
*/
issuer?: string;
/**
* The Client ID found on your Application settings page
*/
clientId: string;
/**
* The value in seconds used to account for clock skew in JWT expirations.
* Typically, this value is no more than a minute or two at maximum.
* Defaults to 60s.
*/
leeway?: number;
/**
* The location to use when storing cache data. Valid values are `memory` or `localstorage`.
* The default setting is `memory`.
*
* Read more about [changing storage options in the Auth0 docs](https://auth0.com/docs/libraries/auth0-single-page-app-sdk#change-storage-options)
*/
cacheLocation?: CacheLocation;
/**
* Specify a custom cache implementation to use for token storage and retrieval. This setting takes precedence over `cacheLocation` if they are both specified.
*/
cache?: ICache;
/**
* If true, refresh tokens are used to fetch new access tokens from the Auth0 server. If false, the legacy technique of using a hidden iframe and the `authorization_code` grant with `prompt=none` is used.
* The default setting is `false`.
*
* **Note**: Use of refresh tokens must be enabled by an administrator on your Auth0 client application.
*/
useRefreshTokens?: boolean;
/**
* 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. If false, the iframe fallback is not used and
* errors relating to a failed `refresh_token` grant should be handled appropriately. The default setting is `false`.
*
* **Note**: There might be situations where doing silent auth with a Web Message response from an iframe is not possible,
* like when you're serving your application from the file system or a custom protocol (like in a Desktop or Native app).
* In situations like this you can disable the iframe fallback and handle the failed `refresh_token` grant and prompt the user to login interactively with `loginWithRedirect` or `loginWithPopup`."
*
* E.g. Using the `file:` protocol in an Electron application does not support that legacy technique.
*
* @example
* let token: string;
* try {
* token = await auth0.getTokenSilently();
* } catch (e) {
* if (e.error === 'missing_refresh_token' || e.error === 'invalid_grant') {
* auth0.loginWithRedirect();
* }
* }
*/
useRefreshTokensFallback?: boolean;
/**
* A maximum number of seconds to wait before declaring background calls to /authorize as failed for timeout
* Defaults to 60s.
*/
authorizeTimeoutInSeconds?: number;
/**
* Specify the timeout for HTTP calls using `fetch`. The default is 10 seconds.
*/
httpTimeoutInSeconds?: number;
/**
* Internal property to send information about the client to the authorization server.
* @internal
*/
auth0Client?: {
name: string;
version: string;
env?: { [key: string]: string };
};
/**
* Sets an additional cookie with no SameSite attribute to support legacy browsers
* that are not compatible with the latest SameSite changes.
* This will log a warning on modern browsers, you can disable the warning by setting
* this to false but be aware that some older useragents will not work,
* See https://www.chromium.org/updates/same-site/incompatible-clients
* Defaults to true
*/
legacySameSiteCookie?: boolean;
/**
* If `true`, the SDK will use a cookie when storing information about the auth transaction while
* the user is going through the authentication flow on the authorization server.
*
* The default is `false`, in which case the SDK will use session storage.
*
* @notes
*
* You might want to enable this if you rely on your users being able to authenticate using flows that
* may end up spanning across multiple tabs (e.g. magic links) or you cannot otherwise rely on session storage being available.
*/
useCookiesForTransactions?: boolean;
/**
* Number of days until the cookie `auth0.is.authenticated` will expire
* Defaults to 1.
*/
sessionCheckExpiryDays?: number;
/**
* The domain the cookie is accessible from. If not set, the cookie is scoped to
* the current domain, including the subdomain.
*
* Note: setting this incorrectly may cause silent authentication to stop working
* on page load.
*
*
* To keep a user logged in across multiple subdomains set this to your
* top-level domain and prefixed with a `.` (eg: `.example.com`).
*/
cookieDomain?: string;
/**
* If true, data to the token endpoint is transmitted as x-www-form-urlencoded data, if false it will be transmitted as JSON. The default setting is `true`.
*
* **Note:** Setting this to `false` may affect you if you use Auth0 Rules and are sending custom, non-primitive data. If you disable this,
* please verify that your Auth0 Rules continue to work as intended.
*/
useFormData?: boolean;
/**
* Modify the value used as the current time during the token validation.
*
* **Note**: Using this improperly can potentially compromise the token validation.
*/
nowProvider?: () => Promise<number> | number;
/**
* If provided, the SDK will load the token worker from this URL instead of the integrated `blob`. An example of when this is useful is if you have strict
* Content-Security-Policy (CSP) and wish to avoid needing to set `worker-src: blob:`. We recommend either serving the worker, which you can find in the module
* at `<module_path>/dist/auth0-spa-js.worker.production.js`, from the same host as your application or using the Auth0 CDN
* `https://cdn.auth0.com/js/auth0-spa-js/<version>/auth0-spa-js.worker.production.js`.
*
* **Note**: The worker is only used when `useRefreshTokens: true`, `cacheLocation: 'memory'`, and the `cache` is not custom.
*/
workerUrl?: string;
/**
* If `true`, the SDK will allow the refreshing of tokens using MRRT
*/
useMrrt?: boolean;
/**
* If `true`, DPoP (OAuth 2.0 Demonstrating Proof of Possession, RFC9449)
* will be used to cryptographically bind tokens to this specific browser
* so they can't be used from a different device in case of a leak.
*
* The default setting is `false`.
*/
useDpop?: boolean;
/**
* URL parameters that will be sent back to the Authorization Server. This can be known parameters
* defined by Auth0 or custom parameters that you define.
*/
authorizationParams?: ClientAuthorizationParams;
}
/**
* The possible locations where tokens can be stored
*/
export type CacheLocation = 'memory' | 'localstorage';
/**
* @ignore
*/
export interface AuthorizeOptions extends AuthorizationParams {
response_type: string;
response_mode: string;
redirect_uri?: string;
nonce: string;
state: string;
scope: string;
code_challenge: string;
code_challenge_method: string;
}
export interface RedirectLoginOptions<TAppState = any>
extends BaseLoginOptions {
/**
* Used to store state before doing the redirect
*/
appState?: TAppState;
/**
* Used to add to the URL fragment before redirecting
*/
fragment?: string;
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* @example
* const client = new Auth0Client({
* async onRedirect(url) {
* window.location.replace(url);
* }
* });
* @deprecated since v2.0.1, use `openUrl` instead.
*/
onRedirect?: (url: string) => Promise<void>;
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* @example
* const client = new Auth0Client({
* openUrl(url) {
* window.location.replace(url);
* }
* });
*
* @example
* import { Browser } from '@capacitor/browser';
*
* const client = new Auth0Client({
* async openUrl(url) {
* await Browser.open({ url });
* }
* });
*/
openUrl?: (url: string) => Promise<void> | void;
}
/**
* The types of responses expected from the authorization server.
* - `code`: used for the standard login flow.
* - `connect_code`: used for the connect account flow.
*/
export enum ResponseType {
Code = 'code',
ConnectCode = 'connect_code'
}
export interface RedirectLoginResult<TAppState = any> {
/**
* State stored when the redirect request was made
*/
appState?: TAppState;
/**
* The type of response, for login it will be `code`
*/
response_type: ResponseType.Code;
}
export interface PopupLoginOptions extends BaseLoginOptions { }
export interface PopupConfigOptions {
/**
* The number of seconds to wait for a popup response before
* throwing a timeout error. Defaults to 60s
*/
timeoutInSeconds?: number;
/**
* Accepts an already-created popup window to use. If not specified, the SDK
* will create its own. This may be useful for platforms like iOS that have
* security restrictions around when popups can be invoked (e.g. from a user click event)
*/
popup?: any;
}
export interface GetTokenSilentlyOptions {
/**
* When `off`, ignores the cache and always sends a
* request to Auth0.
* When `cache-only`, only reads from the cache and never sends a request to Auth0.
* Defaults to `on`, where it both reads from the cache and sends a request to Auth0 as needed.
*/
cacheMode?: 'on' | 'off' | 'cache-only';
/**
* Parameters that will be sent back to Auth0 as part of a request.
*/
authorizationParams?: {
/**
* There's no actual redirect when getting a token silently,
* but, according to the spec, a `redirect_uri` param is required.
* Auth0 uses this parameter to validate that the current `origin`
* matches the `redirect_uri` `origin` when sending the response.
* It must be whitelisted in the "Allowed Web Origins" in your
* Auth0 Application's settings.
*/
redirect_uri?: string;
/**
* The scope that was used in the authentication request
*/
scope?: string;
/**
* The audience that was used in the authentication request
*/
audience?: string;
/**
* If you need to send custom parameters to the Authorization Server,
* make sure to use the original parameter name.
*/
[key: string]: any;
};
/** A maximum number of seconds to wait before declaring the background /authorize call as failed for timeout
* Defaults to 60s.
*/
timeoutInSeconds?: number;
/**
* If true, the full response from the /oauth/token endpoint (or the cache, if the cache was used) is returned
* (minus `refresh_token` if one was issued). Otherwise, just the access token is returned.
*
* The default is `false`.
*/
detailedResponse?: boolean;
}
export interface GetTokenWithPopupOptions extends PopupLoginOptions {
/**
* When `off`, ignores the cache and always sends a request to Auth0.
* When `cache-only`, only reads from the cache and never sends a request to Auth0.
* Defaults to `on`, where it both reads from the cache and sends a request to Auth0 as needed.
*/
cacheMode?: 'on' | 'off' | 'cache-only';
}
export interface LogoutUrlOptions {
/**
* The `clientId` of your application.
*
* If this property is not set, then the `clientId` that was used during initialization of the SDK is sent to the logout endpoint.
*
* If this property is set to `null`, then no client ID value is sent to the logout endpoint.
*
* [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
*/
clientId?: string | null;
/**
* Parameters to pass to the logout endpoint. This can be known parameters defined by Auth0 or custom parameters
* you wish to provide.
*/
logoutParams?: {
/**
* When supported by the upstream identity provider,
* forces the user to logout of their identity provider
* and from Auth0.
* [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps)
*/
federated?: boolean;
/**
* The URL where Auth0 will redirect your browser to after the logout.
*
* **Note**: If the `client_id` parameter is included, the
* `returnTo` URL that is provided must be listed in the
* Application's "Allowed Logout URLs" in the Auth0 dashboard.
* However, if the `client_id` parameter is not included, the
* `returnTo` URL must be listed in the "Allowed Logout URLs" at
* the account level in the Auth0 dashboard.
*
* [Read more about how redirecting after logout works](https://auth0.com/docs/logout/guides/redirect-users-after-logout)
*/
returnTo?: string;
/**
* If you need to send custom parameters to the logout endpoint, make sure to use the original parameter name.
*/
[key: string]: any;
};
}
export interface LogoutOptions extends LogoutUrlOptions {
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* @example
* await auth0.logout({
* async onRedirect(url) {
* window.location.replace(url);
* }
* });
* @deprecated since v2.0.1, use `openUrl` instead.
*/
onRedirect?: (url: string) => Promise<void>;
/**
* Used to control the redirect and not rely on the SDK to do the actual redirect.
*
* Set to `false` to disable the redirect, or provide a function to handle the actual redirect yourself.
*
* @example
* await auth0.logout({
* openUrl(url) {
* window.location.replace(url);
* }
* });
*
* @example
* import { Browser } from '@capacitor/browser';
*
* await auth0.logout({
* async openUrl(url) {
* await Browser.open({ url });
* }
* });
*/
openUrl?: false | ((url: string) => Promise<void> | void);
}
export interface RedirectConnectAccountOptions<TAppState = any> {
/**
* The name of the connection to link (e.g. 'google-oauth2').
*/
connection: string;
/**
* Array of scopes to request from the Identity Provider during the connect account flow.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'google-oauth2',
* scopes: ['https://www.googleapis.com/auth/calendar']
* });
*/
scopes?: string[];
/**
* Additional authorization parameters for the request.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'github',
* authorization_params: {
* audience: 'https://api.github.com'
* }
* });
*/
authorization_params?: AuthorizationParams;
/**
* The URI to redirect back to after connecting the account.
*/
redirectUri?: string;
/**
* Optional application state to persist through the transaction.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'google-oauth2',
* appState: { returnTo: '/settings' }
* });
*/
appState?: TAppState;
/**
* Optional function to handle the redirect URL.
*
* @example
* await auth0.connectAccountWithRedirect({
* connection: 'google-oauth2',
* openUrl: async (url) => { myBrowserApi.open(url); }
* });
*/
openUrl?: (url: string) => Promise<void>;
}
/**
* The result returned after a successful account connection redirect.
*
* Combines the redirect login result (including any persisted app state)
* with the complete response from the My Account API.
*
* @template TAppState - The type of application state persisted through the transaction.
* @example
* const result = await auth0.connectAccountWithRedirect(options);
* console.log(result.appState); // Access persisted app state
* console.log(result.connection); // The connection of the account you connected to.
* console.log(result.response_type === 'connect_code'); // The response type will be 'connect_code'
*/
export type ConnectAccountRedirectResult<TAppState = any> = CompleteResponse & {
/**
* State stored when the redirect request was made
*/
appState?: TAppState;
/**
* The type of response, for connect account it will be `connect_code`
*/
response_type: ResponseType.ConnectCode;
};
/**
* @ignore
*/
export interface AuthenticationResult {
state: string;
code?: string;
/**
* This is for the redirect from the connect account flow.
*/
connect_code?: string;
error?: string;
error_description?: string;
}
/**
* @ignore
*/
export interface TokenEndpointOptions {
baseUrl: string;
client_id: string;
grant_type: string;
timeout?: number;
auth0Client: any;
useFormData?: boolean;
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>;
[key: string]: any;
}
export type TokenEndpointResponse = {
id_token: string;
token_type: string;
access_token: string;
refresh_token?: string;
expires_in: number;
scope?: string;
};
/**
* @ignore
*/
export interface OAuthTokenOptions extends TokenEndpointOptions {
code_verifier: string;
code: string;
redirect_uri: string;
audience: string;
scope: string;
}
/**
* @ignore
*/
export interface RefreshTokenOptions extends TokenEndpointOptions {
refresh_token: string;
}
/**
* @ignore
*/
export interface JWTVerifyOptions {
iss: string;
aud: string;
id_token: string;
nonce?: string;
leeway?: number;
max_age?: number;
organization?: string;
now?: number;
}
export interface IdToken {
__raw: string;
name?: string;
given_name?: string;
family_name?: string;
middle_name?: string;
nickname?: string;
preferred_username?: string;
profile?: string;
picture?: string;
website?: string;
email?: string;
email_verified?: boolean;
gender?: string;
birthdate?: string;
zoneinfo?: string;
locale?: string;
phone_number?: string;
phone_number_verified?: boolean;
address?: string;
updated_at?: string;
iss?: string;
aud?: string;
exp?: number;
nbf?: number;
iat?: number;
jti?: string;
azp?: string;
nonce?: string;
auth_time?: string;
at_hash?: string;
c_hash?: string;
acr?: string;
amr?: string[];
sub_jwk?: string;
cnf?: string;
sid?: string;
org_id?: string;
org_name?: string;
[key: string]: any;
}
export class User {
name?: string;
given_name?: string;
family_name?: string;
middle_name?: string;
nickname?: string;
preferred_username?: string;
profile?: string;
picture?: string;
website?: string;
email?: string;
email_verified?: boolean;
gender?: string;
birthdate?: string;
zoneinfo?: string;
locale?: string;
phone_number?: string;
phone_number_verified?: boolean;
address?: string;
updated_at?: string;
sub?: string;
[key: string]: any;
}
/**
* @ignore
*/
export type FetchOptions = {
method?: string;
headers?: Record<string, string>;
credentials?: 'include' | 'omit';
body?: string;
signal?: AbortSignal;
};
/**
* @ignore
*/
export type FetchResponse = {
ok: boolean;
headers: Record<string, string | undefined>;
json: any;
};
export type GetTokenSilentlyVerboseResponse = Omit<
TokenEndpointResponse,
'refresh_token'
>;
+229
View File
@@ -0,0 +1,229 @@
import {
DEFAULT_FETCH_TIMEOUT_MS,
DEFAULT_SILENT_TOKEN_RETRY_COUNT
} from './constants';
import { fromEntries } from './utils';
import { sendMessage } from './worker/worker.utils';
import { FetchOptions, FetchResponse } from './global';
import {
GenericError,
MfaRequiredError,
MissingRefreshTokenError,
UseDpopNonceError
} from './errors';
import { Dpop } from './dpop/dpop';
import { DPOP_NONCE_HEADER } from './dpop/utils';
export const createAbortController = () => new AbortController();
const dofetch = async (fetchUrl: string, fetchOptions: FetchOptions) => {
const response = await fetch(fetchUrl, fetchOptions);
return {
ok: response.ok,
json: await response.json(),
/**
* This is not needed, but do it anyway so the object shape is the
* same as when using a Web Worker (which *does* need this, see
* src/worker/token.worker.ts).
*/
headers: fromEntries(response.headers)
};
};
const fetchWithoutWorker = async (
fetchUrl: string,
fetchOptions: FetchOptions,
timeout: number
) => {
const controller = createAbortController();
fetchOptions.signal = controller.signal;
let timeoutId: NodeJS.Timeout;
// The promise will resolve with one of these two promises (the fetch or the timeout), whichever completes first.
return Promise.race([
dofetch(fetchUrl, fetchOptions),
new Promise((_, reject) => {
timeoutId = setTimeout(() => {
controller.abort();
reject(new Error("Timeout when executing 'fetch'"));
}, timeout);
})
]).finally(() => {
clearTimeout(timeoutId);
});
};
const fetchWithWorker = async (
fetchUrl: string,
audience: string,
scope: string,
fetchOptions: FetchOptions,
timeout: number,
worker: Worker,
useFormData?: boolean,
useMrrt?: boolean
) => {
return sendMessage(
{
auth: {
audience,
scope
},
timeout,
fetchUrl,
fetchOptions,
useFormData,
useMrrt
},
worker
);
};
export const switchFetch = async (
fetchUrl: string,
audience: string,
scope: string,
fetchOptions: FetchOptions,
worker?: Worker,
useFormData?: boolean,
timeout = DEFAULT_FETCH_TIMEOUT_MS,
useMrrt?: boolean,
): Promise<any> => {
if (worker) {
return fetchWithWorker(
fetchUrl,
audience,
scope,
fetchOptions,
timeout,
worker,
useFormData,
useMrrt
);
} else {
return fetchWithoutWorker(fetchUrl, fetchOptions, timeout);
}
};
export async function getJSON<T>(
url: string,
timeout: number | undefined,
audience: string,
scope: string,
options: FetchOptions,
worker?: Worker,
useFormData?: boolean,
useMrrt?: boolean,
dpop?: Pick<Dpop, 'generateProof' | 'getNonce' | 'setNonce'>,
isDpopRetry?: boolean
): Promise<T> {
if (dpop) {
const dpopProof = await dpop.generateProof({
url,
method: options.method || 'GET',
nonce: await dpop.getNonce()
});
options.headers = { ...options.headers, dpop: dpopProof };
}
let fetchError: null | Error = null;
let response!: FetchResponse;
for (let i = 0; i < DEFAULT_SILENT_TOKEN_RETRY_COUNT; i++) {
try {
response = await switchFetch(
url,
audience,
scope,
options,
worker,
useFormData,
timeout,
useMrrt,
);
fetchError = null;
break;
} catch (e) {
// Fetch only fails in the case of a network issue, so should be
// retried here. Failure status (4xx, 5xx, etc) return a resolved Promise
// with the failure in the body.
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
fetchError = e;
}
}
if (fetchError) {
throw fetchError;
}
const {
json: { error, error_description, ...data },
headers,
ok
} = response;
let newDpopNonce: string | undefined;
if (dpop) {
/**
* Note that a new DPoP nonce can appear in both error and success responses!
*
* @see {@link https://www.rfc-editor.org/rfc/rfc9449.html#section-8.2-3}
*/
newDpopNonce = headers[DPOP_NONCE_HEADER];
if (newDpopNonce) {
await dpop.setNonce(newDpopNonce);
}
}
if (!ok) {
const errorMessage =
error_description || `HTTP error. Unable to fetch ${url}`;
if (error === 'mfa_required') {
throw new MfaRequiredError(error, errorMessage, data.mfa_token);
}
if (error === 'missing_refresh_token') {
throw new MissingRefreshTokenError(audience, scope);
}
/**
* When DPoP is used and we get a `use_dpop_nonce` error from the server,
* we must retry ONCE with any new nonce received in the rejected request.
*
* If a new nonce was not received or the retry fails again, we give up and
* throw the error as is.
*/
if (error === 'use_dpop_nonce') {
if (!dpop || !newDpopNonce || isDpopRetry) {
throw new UseDpopNonceError(newDpopNonce);
}
// repeat the call but with isDpopRetry=true to avoid any more retries
return getJSON(
url,
timeout,
audience,
scope,
options,
worker,
useFormData,
useMrrt,
dpop,
true // !
);
}
throw new GenericError(error || 'request_error', errorMessage);
}
return data;
}
+55
View File
@@ -0,0 +1,55 @@
import { Auth0Client } from './Auth0Client';
import { Auth0ClientOptions } from './global';
import './global';
export * from './global';
/**
* Asynchronously creates the Auth0Client instance and calls `checkSession`.
*
* **Note:** There are caveats to using this in a private browser tab, which may not silently authenticae
* a user on page refresh. Please see [the checkSession docs](https://auth0.github.io/auth0-spa-js/classes/Auth0Client.html#checksession) for more info.
*
* @param options The client options
* @returns An instance of Auth0Client
*/
export async function createAuth0Client(options: Auth0ClientOptions) {
const auth0 = new Auth0Client(options);
await auth0.checkSession();
return auth0;
}
export { Auth0Client };
export {
ConnectError,
GenericError,
AuthenticationError,
TimeoutError,
PopupTimeoutError,
PopupCancelledError,
MfaRequiredError,
MissingRefreshTokenError,
UseDpopNonceError
} from './errors';
export {
ICache,
LocalStorageCache,
InMemoryCache,
Cacheable,
DecodedToken,
CacheEntry,
WrappedCacheEntry,
KeyManifestEntry,
MaybePromise,
CacheKey,
CacheKeyData
} from './cache';
export { type FetcherConfig } from './fetcher';
export { MyAccountApiError } from './MyAccountApiClient';
export { CustomTokenExchangeOptions } from './TokenExchange';
+225
View File
@@ -0,0 +1,225 @@
import { urlDecodeB64 } from './utils';
import { IdToken, JWTVerifyOptions } from './global';
const isNumber = (n: any) => typeof n === 'number';
const idTokendecoded = [
'iss',
'aud',
'exp',
'nbf',
'iat',
'jti',
'azp',
'nonce',
'auth_time',
'at_hash',
'c_hash',
'acr',
'amr',
'sub_jwk',
'cnf',
'sip_from_tag',
'sip_date',
'sip_callid',
'sip_cseq_num',
'sip_via_branch',
'orig',
'dest',
'mky',
'events',
'toe',
'txn',
'rph',
'sid',
'vot',
'vtm'
];
export const decode = (token: string) => {
const parts = token.split('.');
const [header, payload, signature] = parts;
if (parts.length !== 3 || !header || !payload || !signature) {
throw new Error('ID token could not be decoded');
}
const payloadJSON = JSON.parse(urlDecodeB64(payload));
const claims: IdToken = { __raw: token };
const user: any = {};
Object.keys(payloadJSON).forEach(k => {
claims[k] = payloadJSON[k];
if (!idTokendecoded.includes(k)) {
user[k] = payloadJSON[k];
}
});
return {
encoded: { header, payload, signature },
header: JSON.parse(urlDecodeB64(header)),
claims,
user
};
};
export const verify = (options: JWTVerifyOptions) => {
if (!options.id_token) {
throw new Error('ID token is required but missing');
}
const decoded = decode(options.id_token);
if (!decoded.claims.iss) {
throw new Error(
'Issuer (iss) claim must be a string present in the ID token'
);
}
if (decoded.claims.iss !== options.iss) {
throw new Error(
`Issuer (iss) claim mismatch in the ID token; expected "${options.iss}", found "${decoded.claims.iss}"`
);
}
if (!decoded.user.sub) {
throw new Error(
'Subject (sub) claim must be a string present in the ID token'
);
}
if (decoded.header.alg !== 'RS256') {
throw new Error(
`Signature algorithm of "${decoded.header.alg}" is not supported. Expected the ID token to be signed with "RS256".`
);
}
if (
!decoded.claims.aud ||
!(
typeof decoded.claims.aud === 'string' ||
Array.isArray(decoded.claims.aud)
)
) {
throw new Error(
'Audience (aud) claim must be a string or array of strings present in the ID token'
);
}
if (Array.isArray(decoded.claims.aud)) {
if (!decoded.claims.aud.includes(options.aud)) {
throw new Error(
`Audience (aud) claim mismatch in the ID token; expected "${
options.aud
}" but was not one of "${decoded.claims.aud.join(', ')}"`
);
}
if (decoded.claims.aud.length > 1) {
if (!decoded.claims.azp) {
throw new Error(
'Authorized Party (azp) claim must be a string present in the ID token when Audience (aud) claim has multiple values'
);
}
if (decoded.claims.azp !== options.aud) {
throw new Error(
`Authorized Party (azp) claim mismatch in the ID token; expected "${options.aud}", found "${decoded.claims.azp}"`
);
}
}
} else if (decoded.claims.aud !== options.aud) {
throw new Error(
`Audience (aud) claim mismatch in the ID token; expected "${options.aud}" but found "${decoded.claims.aud}"`
);
}
if (options.nonce) {
if (!decoded.claims.nonce) {
throw new Error(
'Nonce (nonce) claim must be a string present in the ID token'
);
}
if (decoded.claims.nonce !== options.nonce) {
throw new Error(
`Nonce (nonce) claim mismatch in the ID token; expected "${options.nonce}", found "${decoded.claims.nonce}"`
);
}
}
if (options.max_age && !isNumber(decoded.claims.auth_time)) {
throw new Error(
'Authentication Time (auth_time) claim must be a number present in the ID token when Max Age (max_age) is specified'
);
}
/* c8 ignore next 5 */
if (decoded.claims.exp == null || !isNumber(decoded.claims.exp)) {
throw new Error(
'Expiration Time (exp) claim must be a number present in the ID token'
);
}
if (!isNumber(decoded.claims.iat)) {
throw new Error(
'Issued At (iat) claim must be a number present in the ID token'
);
}
const leeway = options.leeway || 60;
const now = new Date(options.now || Date.now());
const expDate = new Date(0);
expDate.setUTCSeconds(decoded.claims.exp + leeway);
if (now > expDate) {
throw new Error(
`Expiration Time (exp) claim error in the ID token; current time (${now}) is after expiration time (${expDate})`
);
}
if (decoded.claims.nbf != null && isNumber(decoded.claims.nbf)) {
const nbfDate = new Date(0);
nbfDate.setUTCSeconds(decoded.claims.nbf - leeway);
if (now < nbfDate) {
throw new Error(
`Not Before time (nbf) claim in the ID token indicates that this token can't be used just yet. Current time (${now}) is before ${nbfDate}`
);
}
}
if (decoded.claims.auth_time != null && isNumber(decoded.claims.auth_time)) {
const authTimeDate = new Date(0);
authTimeDate.setUTCSeconds(
parseInt(decoded.claims.auth_time) + (options.max_age as number) + leeway
);
if (now > authTimeDate) {
throw new Error(
`Authentication Time (auth_time) claim in the ID token indicates that too much time has passed since the last end-user authentication. Current time (${now}) is after last auth at ${authTimeDate}`
);
}
}
if (options.organization) {
const org = options.organization.trim();
if (org.startsWith('org_')) {
const orgId = org;
if (!decoded.claims.org_id) {
throw new Error(
'Organization ID (org_id) claim must be a string present in the ID token'
);
} else if (orgId !== decoded.claims.org_id) {
throw new Error(
`Organization ID (org_id) claim mismatch in the ID token; expected "${orgId}", found "${decoded.claims.org_id}"`
);
}
} else {
const orgName = org.toLowerCase();
// TODO should we verify if there is an `org_id` claim?
if (!decoded.claims.org_name) {
throw new Error(
'Organization Name (org_name) claim must be a string present in the ID token'
);
} else if (orgName !== decoded.claims.org_name) {
throw new Error(
`Organization Name (org_name) claim mismatch in the ID token; expected "${orgName}", found "${decoded.claims.org_name}"`
);
}
}
}
return decoded;
};
+29
View File
@@ -0,0 +1,29 @@
const singlePromiseMap: Record<string, Promise<any>> = {};
export const singlePromise = <T>(
cb: () => Promise<T>,
key: string
): Promise<T> => {
let promise: null | Promise<T> = singlePromiseMap[key];
if (!promise) {
promise = cb().finally(() => {
delete singlePromiseMap[key];
promise = null;
});
singlePromiseMap[key] = promise;
}
return promise;
};
export const retryPromise = async (
cb: () => Promise<boolean>,
maxNumberOfRetries = 3
) => {
for (let i = 0; i < maxNumberOfRetries; i++) {
if (await cb()) {
return true;
}
}
return false;
};
+74
View File
@@ -0,0 +1,74 @@
import { DEFAULT_AUDIENCE } from "./constants";
/**
* @ignore
*/
const dedupe = (arr: string[]) => Array.from(new Set(arr));
/**
* @ignore
*/
/**
* Returns a string of unique scopes by removing duplicates and unnecessary whitespace.
*
* @param {...(string | undefined)[]} scopes - A list of scope strings or undefined values.
* @returns {string} A string containing unique scopes separated by a single space.
*/
export const getUniqueScopes = (...scopes: (string | undefined)[]) => {
return dedupe(scopes.filter(Boolean).join(' ').trim().split(/\s+/)).join(' ');
};
/**
* @ignore
*/
/**
* We will check if the developer has created the client with a string or object of audience:scopes. We will inject
* the base scopes to each audience, and store the base ones inside default key. As well, if the developer created the Auth0Client
* with a string of scopes, we will store the requested ones with the base scopes inside the default key as well.
* @param authScopes The scopes requested by the user when creating the Auth0Client
* @param openIdScope openId scope
* @param extraScopes Other scopes to accumulate such as offline_access
* @returns {Record<string, string>} An object with all scopes that are going to be accumulated.
*/
export const injectDefaultScopes = (authScopes: string | Record<string, string> | undefined, openIdScope: string, ...extraScopes: string[]): Record<string, string> => {
if (typeof authScopes !== 'object') {
return { [DEFAULT_AUDIENCE]: getUniqueScopes(openIdScope, authScopes, ...extraScopes) };
}
let requestedScopes: Record<string, string> = {
[DEFAULT_AUDIENCE]: getUniqueScopes(openIdScope, ...extraScopes),
};
Object.keys(authScopes).forEach((key) => {
const audienceScopes = authScopes[key];
requestedScopes[key] = getUniqueScopes(openIdScope, audienceScopes, ...extraScopes);
});
return requestedScopes;
}
/**
* @ignore
*/
/**
* Will return a string of scopes. If a specific audience was requested and it exist inside the scopes object, we will return those
* related to that audience that we want to accumulate. If not, we will return the ones stored inside the default key.
* @param authScopes Object of audience:scopes that are going to be accumulated
* @param methodScopes The scopes requested for the developer in a specific request
* @param audience The audience the developer requested for an specific request or the one they configured in the Auth0Client
* @returns {string} A combination of Auth0Client scopes and the ones requested by the developer for a specific request
*/
export const scopesToRequest = (authScopes: Record<string, string>, methodScopes: string | undefined, audience: string | undefined): string => {
let scope: string | undefined;
if (audience) {
scope = authScopes[audience];
}
if (!scope) {
scope = authScopes[DEFAULT_AUDIENCE];
}
return getUniqueScopes(scope, methodScopes);
}
+145
View File
@@ -0,0 +1,145 @@
import * as Cookies from 'es-cookie';
interface ClientStorageOptions {
daysUntilExpire?: number;
cookieDomain?: string;
}
/**
* Defines a type that handles storage to/from a storage location
*/
export type ClientStorage = {
get<T extends Object>(key: string): T | undefined;
save(key: string, value: any, options?: ClientStorageOptions): void;
remove(key: string, options?: ClientStorageOptions): void;
};
/**
* A storage protocol for marshalling data to/from cookies
*/
export const CookieStorage = {
get<T extends Object>(key: string) {
const value = Cookies.get(key);
if (typeof value === 'undefined') {
return;
}
return <T>JSON.parse(value);
},
save(key: string, value: any, options?: ClientStorageOptions): void {
let cookieAttributes: Cookies.CookieAttributes = {};
if ('https:' === window.location.protocol) {
cookieAttributes = {
secure: true,
sameSite: 'none'
};
}
if (options?.daysUntilExpire) {
cookieAttributes.expires = options.daysUntilExpire;
}
if (options?.cookieDomain) {
cookieAttributes.domain = options.cookieDomain;
}
Cookies.set(key, JSON.stringify(value), cookieAttributes);
},
remove(key: string, options?: ClientStorageOptions) {
let cookieAttributes: Cookies.CookieAttributes = {};
if (options?.cookieDomain) {
cookieAttributes.domain = options.cookieDomain;
}
Cookies.remove(key, cookieAttributes);
}
} as ClientStorage;
/**
* @ignore
*/
const LEGACY_PREFIX = '_legacy_';
/**
* Cookie storage that creates a cookie for modern and legacy browsers.
* See: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
*/
export const CookieStorageWithLegacySameSite = {
get<T extends Object>(key: string) {
const value = CookieStorage.get<T>(key);
if (value) {
return value;
}
return CookieStorage.get<T>(`${LEGACY_PREFIX}${key}`);
},
save(key: string, value: any, options?: ClientStorageOptions): void {
let cookieAttributes: Cookies.CookieAttributes = {};
if ('https:' === window.location.protocol) {
cookieAttributes = { secure: true };
}
if (options?.daysUntilExpire) {
cookieAttributes.expires = options.daysUntilExpire;
}
if (options?.cookieDomain) {
cookieAttributes.domain = options.cookieDomain;
}
Cookies.set(
`${LEGACY_PREFIX}${key}`,
JSON.stringify(value),
cookieAttributes
);
CookieStorage.save(key, value, options);
},
remove(key: string, options?: ClientStorageOptions) {
let cookieAttributes: Cookies.CookieAttributes = {};
if (options?.cookieDomain) {
cookieAttributes.domain = options.cookieDomain;
}
Cookies.remove(key, cookieAttributes);
CookieStorage.remove(key, options);
CookieStorage.remove(`${LEGACY_PREFIX}${key}`, options);
}
} as ClientStorage;
/**
* A storage protocol for marshalling data to/from session storage
*/
export const SessionStorage = {
get<T extends Object>(key: string) {
/* c8 ignore next 3 */
if (typeof sessionStorage === 'undefined') {
return;
}
const value = sessionStorage.getItem(key);
if (value == null) {
return;
}
return <T>JSON.parse(value);
},
save(key: string, value: any): void {
sessionStorage.setItem(key, JSON.stringify(value));
},
remove(key: string) {
sessionStorage.removeItem(key);
}
} as ClientStorage;
+56
View File
@@ -0,0 +1,56 @@
import { ClientStorage } from './storage';
const TRANSACTION_STORAGE_KEY_PREFIX = 'a0.spajs.txs';
export interface LoginTransaction {
nonce: string;
scope: string;
audience: string;
appState?: any;
code_verifier: string;
redirect_uri?: string;
organization?: string;
state?: string;
response_type: 'code';
}
export interface ConnectAccountTransaction {
appState?: any;
audience?: string;
auth_session: string;
code_verifier: string;
redirect_uri: string;
scope?: string;
state: string;
connection: string;
response_type: 'connect_code';
}
export class TransactionManager {
private storageKey: string;
constructor(
private storage: ClientStorage,
private clientId: string,
private cookieDomain?: string
) {
this.storageKey = `${TRANSACTION_STORAGE_KEY_PREFIX}.${this.clientId}`;
}
public create<T extends Object = LoginTransaction>(transaction: T) {
this.storage.save(this.storageKey, transaction, {
daysUntilExpire: 1,
cookieDomain: this.cookieDomain
});
}
public get<T extends Object = LoginTransaction>(): T | undefined {
return this.storage.get(this.storageKey);
}
public remove() {
this.storage.remove(this.storageKey, {
cookieDomain: this.cookieDomain
});
}
}
+300
View File
@@ -0,0 +1,300 @@
import { AuthenticationResult, PopupConfigOptions } from './global';
import {
DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS,
CLEANUP_IFRAME_TIMEOUT_IN_SECONDS
} from './constants';
import {
PopupTimeoutError,
TimeoutError,
GenericError,
PopupCancelledError
} from './errors';
export const parseAuthenticationResult = (
queryString: string
): AuthenticationResult => {
if (queryString.indexOf('#') > -1) {
queryString = queryString.substring(0, queryString.indexOf('#'));
}
const searchParams = new URLSearchParams(queryString);
return {
state: searchParams.get('state')!,
code: searchParams.get('code') || undefined,
connect_code: searchParams.get('connect_code') || undefined,
error: searchParams.get('error') || undefined,
error_description: searchParams.get('error_description') || undefined
};
};
export const runIframe = (
authorizeUrl: string,
eventOrigin: string,
timeoutInSeconds: number = DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS
) => {
return new Promise<AuthenticationResult>((res, rej) => {
const iframe = window.document.createElement('iframe');
iframe.setAttribute('width', '0');
iframe.setAttribute('height', '0');
iframe.style.display = 'none';
const removeIframe = () => {
if (window.document.body.contains(iframe)) {
window.document.body.removeChild(iframe);
window.removeEventListener('message', iframeEventHandler, false);
}
};
let iframeEventHandler: (e: MessageEvent) => void;
const timeoutSetTimeoutId = setTimeout(() => {
rej(new TimeoutError());
removeIframe();
}, timeoutInSeconds * 1000);
iframeEventHandler = function (e: MessageEvent) {
if (e.origin != eventOrigin) return;
if (!e.data || e.data.type !== 'authorization_response') return;
const eventSource = e.source;
if (eventSource) {
(eventSource as any).close();
}
e.data.response.error
? rej(GenericError.fromPayload(e.data.response))
: res(e.data.response);
clearTimeout(timeoutSetTimeoutId);
window.removeEventListener('message', iframeEventHandler, false);
// Delay the removal of the iframe to prevent hanging loading status
// in Chrome: https://github.com/auth0/auth0-spa-js/issues/240
setTimeout(removeIframe, CLEANUP_IFRAME_TIMEOUT_IN_SECONDS * 1000);
};
window.addEventListener('message', iframeEventHandler, false);
window.document.body.appendChild(iframe);
iframe.setAttribute('src', authorizeUrl);
});
};
export const openPopup = (url: string) => {
const width = 400;
const height = 600;
const left = window.screenX + (window.innerWidth - width) / 2;
const top = window.screenY + (window.innerHeight - height) / 2;
return window.open(
url,
'auth0:authorize:popup',
`left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1`
);
};
export const runPopup = (config: PopupConfigOptions) => {
return new Promise<AuthenticationResult>((resolve, reject) => {
let popupEventListener: (e: MessageEvent) => void;
// Check each second if the popup is closed triggering a PopupCancelledError
const popupTimer = setInterval(() => {
if (config.popup && config.popup.closed) {
clearInterval(popupTimer);
clearTimeout(timeoutId);
window.removeEventListener('message', popupEventListener, false);
reject(new PopupCancelledError(config.popup));
}
}, 1000);
const timeoutId = setTimeout(() => {
clearInterval(popupTimer);
reject(new PopupTimeoutError(config.popup));
window.removeEventListener('message', popupEventListener, false);
}, (config.timeoutInSeconds || DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS) * 1000);
popupEventListener = function (e: MessageEvent) {
if (!e.data || e.data.type !== 'authorization_response') {
return;
}
clearTimeout(timeoutId);
clearInterval(popupTimer);
window.removeEventListener('message', popupEventListener, false);
config.popup.close();
if (e.data.response.error) {
return reject(GenericError.fromPayload(e.data.response));
}
resolve(e.data.response);
};
window.addEventListener('message', popupEventListener);
});
};
export const getCrypto = () => {
return window.crypto;
};
export const createRandomString = () => {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
let random = '';
const randomValues = Array.from(
getCrypto().getRandomValues(new Uint8Array(43))
);
randomValues.forEach(v => (random += charset[v % charset.length]));
return random;
};
export const encode = (value: string) => btoa(value);
export const decode = (value: string) => atob(value);
const stripUndefined = (params: any) => {
return Object.keys(params)
.filter(k => typeof params[k] !== 'undefined')
.reduce((acc, key) => ({ ...acc, [key]: params[key] }), {});
};
const ALLOWED_AUTH0CLIENT_PROPERTIES = [
{
key: 'name',
type: ['string']
},
{
key: 'version',
type: ['string', 'number']
},
{
key: 'env',
type: ['object']
}
];
/**
* Strips any property that is not present in ALLOWED_AUTH0CLIENT_PROPERTIES
* @param auth0Client - The full auth0Client object
* @returns The stripped auth0Client object
*/
export const stripAuth0Client = (auth0Client: any) => {
return Object.keys(auth0Client).reduce((acc: any, key: string) => {
const allowedProperty = ALLOWED_AUTH0CLIENT_PROPERTIES.find(
p => p.key === key
);
if (
allowedProperty &&
allowedProperty.type.includes(typeof auth0Client[key])
) {
acc[key] = auth0Client[key];
}
return acc;
}, {});
};
export const createQueryParams = ({ clientId: client_id, ...params }: any) => {
return new URLSearchParams(
stripUndefined({ client_id, ...params })
).toString();
};
export const sha256 = async (s: string) => {
const digestOp: any = getCrypto().subtle.digest(
{ name: 'SHA-256' },
new TextEncoder().encode(s)
);
return await digestOp;
};
const urlEncodeB64 = (input: string) => {
const b64Chars: { [index: string]: string } = { '+': '-', '/': '_', '=': '' };
return input.replace(/[+/=]/g, (m: string) => b64Chars[m]);
};
// https://stackoverflow.com/questions/30106476/
const decodeB64 = (input: string) =>
decodeURIComponent(
atob(input)
.split('')
.map(c => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
})
.join('')
);
export const urlDecodeB64 = (input: string) =>
decodeB64(input.replace(/_/g, '/').replace(/-/g, '+'));
export const bufferToBase64UrlEncoded = (input: number[] | Uint8Array) => {
const ie11SafeInput = new Uint8Array(input);
return urlEncodeB64(
window.btoa(String.fromCharCode(...Array.from(ie11SafeInput)))
);
};
export const validateCrypto = () => {
if (!getCrypto()) {
throw new Error(
'For security reasons, `window.crypto` is required to run `auth0-spa-js`.'
);
}
if (typeof getCrypto().subtle === 'undefined') {
throw new Error(`
auth0-spa-js must run on a secure origin. See https://github.com/auth0/auth0-spa-js/blob/main/FAQ.md#why-do-i-get-auth0-spa-js-must-run-on-a-secure-origin for more information.
`);
}
};
/**
* @ignore
*/
export const getDomain = (domainUrl: string) => {
if (!/^https?:\/\//.test(domainUrl)) {
return `https://${domainUrl}`;
}
return domainUrl;
};
/**
* @ignore
*/
export const getTokenIssuer = (
issuer: string | undefined,
domainUrl: string
) => {
if (issuer) {
return issuer.startsWith('https://') ? issuer : `https://${issuer}/`;
}
return `${domainUrl}/`;
};
export const parseNumber = (value: any): number | undefined => {
if (typeof value !== 'string') {
return value;
}
return parseInt(value, 10) || undefined;
};
/**
* Ponyfill for `Object.fromEntries()`, which is not available until ES2020.
*
* When the target of this project reaches ES2020, this can be removed.
*/
export const fromEntries = <T = any>(
iterable: Iterable<[PropertyKey, T]>
): Record<PropertyKey, T> => {
return [...iterable].reduce((obj, [key, val]) => {
obj[key] = val;
return obj;
}, {} as Record<PropertyKey, T>);
};
+1
View File
@@ -0,0 +1 @@
export default '2.9.1';
+10
View File
@@ -0,0 +1,10 @@
const { messageHandler } = jest.requireActual('../token.worker');
export default class {
postMessage(data, ports) {
messageHandler({
data,
ports
});
}
}
+190
View File
@@ -0,0 +1,190 @@
import { MissingRefreshTokenError } from '../errors';
import { FetchResponse } from '../global';
import { createQueryParams, fromEntries } from '../utils';
import { WorkerRefreshTokenMessage } from './worker.types';
let refreshTokens: Record<string, string> = {};
const cacheKey = (audience: string, scope: string) => `${audience}|${scope}`;
const cacheKeyContainsAudience = (audience: string, cacheKey: string) => cacheKey.startsWith(`${audience}|`);
const getRefreshToken = (audience: string, scope: string): string | undefined =>
refreshTokens[cacheKey(audience, scope)];
const setRefreshToken = (
refreshToken: string,
audience: string,
scope: string
) => (refreshTokens[cacheKey(audience, scope)] = refreshToken);
const deleteRefreshToken = (audience: string, scope: string) =>
delete refreshTokens[cacheKey(audience, scope)];
const wait = (time: number) =>
new Promise<void>(resolve => setTimeout(resolve, time));
const formDataToObject = (formData: string): Record<string, any> => {
const queryParams = new URLSearchParams(formData);
const parsedQuery: any = {};
queryParams.forEach((val, key) => {
parsedQuery[key] = val;
});
return parsedQuery;
};
const updateRefreshTokens = (oldRefreshToken: string | undefined, newRefreshToken: string): void => {
Object.entries(refreshTokens).forEach(([key, token]) => {
if (token === oldRefreshToken) {
refreshTokens[key] = newRefreshToken;
}
});
}
const checkDownscoping = (scope: string, audience: string): boolean => {
const findCoincidence = Object.keys(refreshTokens).find((key) => {
if (key !== 'latest_refresh_token') {
const isSameAudience = cacheKeyContainsAudience(audience, key);
const scopesKey = key.split('|')[1].split(" ");
const requestedScopes = scope.split(" ");
const scopesAreIncluded = requestedScopes.every((key) => scopesKey.includes(key));
return isSameAudience && scopesAreIncluded;
}
})
return findCoincidence ? true : false;
}
const messageHandler = async ({
data: { timeout, auth, fetchUrl, fetchOptions, useFormData, useMrrt },
ports: [port]
}: MessageEvent<WorkerRefreshTokenMessage>) => {
let headers: FetchResponse['headers'] = {};
let json: {
refresh_token?: string;
};
let refreshToken: string | undefined;
const { audience, scope } = auth || {};
try {
const body = useFormData
? formDataToObject(fetchOptions.body as string)
: JSON.parse(fetchOptions.body as string);
if (!body.refresh_token && body.grant_type === 'refresh_token') {
refreshToken = getRefreshToken(audience, scope);
// When we don't have any refresh_token that matches the audience and scopes
// stored, and useMrrt is configured to true, we will use the last refresh_token
// returned by the server to do a refresh
// We will avoid doing MRRT if we were to downscope while doing refresh in the same audience
if (!refreshToken && useMrrt) {
const latestRefreshToken = refreshTokens["latest_refresh_token"];
const isDownscoping = checkDownscoping(scope, audience);
if (latestRefreshToken && !isDownscoping) {
refreshToken = latestRefreshToken;
}
}
if (!refreshToken) {
throw new MissingRefreshTokenError(audience, scope);
}
fetchOptions.body = useFormData
? createQueryParams({
...body,
refresh_token: refreshToken
})
: JSON.stringify({
...body,
refresh_token: refreshToken
});
}
let abortController: AbortController | undefined;
if (typeof AbortController === 'function') {
abortController = new AbortController();
fetchOptions.signal = abortController.signal;
}
let response: void | Response;
try {
response = await Promise.race([
wait(timeout),
fetch(fetchUrl, { ...fetchOptions })
]);
} catch (error) {
// fetch error, reject `sendMessage` using `error` key so that we retry.
port.postMessage({
error: error.message
});
return;
}
if (!response) {
// If the request times out, abort it and let `switchFetch` raise the error.
if (abortController) abortController.abort();
port.postMessage({
error: "Timeout when executing 'fetch'"
});
return;
}
headers = fromEntries(response.headers);
json = await response.json();
if (json.refresh_token) {
// If useMrrt is configured to true we want to save the latest refresh_token
// to be used when refreshing tokens with MRRT
if (useMrrt) {
refreshTokens["latest_refresh_token"] = json.refresh_token;
// To avoid having some refresh_token that has already been used
// we will update those inside the list with the new one obtained
// by the server
updateRefreshTokens(refreshToken, json.refresh_token);
}
setRefreshToken(json.refresh_token, audience, scope);
delete json.refresh_token;
} else {
deleteRefreshToken(audience, scope);
}
port.postMessage({
ok: response.ok,
json,
headers
});
} catch (error) {
port.postMessage({
ok: false,
json: {
error: error.error,
error_description: error.message
},
headers
});
}
};
// Don't run `addEventListener` in our tests (this is replaced in rollup)
if (process.env.NODE_ENV === 'test') {
module.exports = { messageHandler };
/* c8 ignore next 4 */
} else {
// @ts-ignore
addEventListener('message', messageHandler);
}
+16
View File
@@ -0,0 +1,16 @@
import { FetchOptions } from '../global';
/**
* @ts-ignore
*/
export type WorkerRefreshTokenMessage = {
timeout: number;
fetchUrl: string;
fetchOptions: FetchOptions;
useFormData?: boolean;
useMrrt?: boolean;
auth: {
audience: string;
scope: string;
};
};
+23
View File
@@ -0,0 +1,23 @@
import { WorkerRefreshTokenMessage } from './worker.types';
/**
* Sends the specified message to the web worker
* @param message The message to send
* @param to The worker to send the message to
*/
export const sendMessage = (message: WorkerRefreshTokenMessage, to: Worker) =>
new Promise(function (resolve, reject) {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function (event) {
// Only for fetch errors, as these get retried
if (event.data.error) {
reject(new Error(event.data.error));
} else {
resolve(event.data);
}
messageChannel.port1.close();
};
to.postMessage(message, [messageChannel.port2]);
});