diff --git a/FAQ.md b/FAQ.md index 2f1130ee9..f54fb88a7 100644 --- a/FAQ.md +++ b/FAQ.md @@ -63,21 +63,19 @@ Note that even though the workaround doesn't cause any weird side effects in bro For more context see this [issue](https://github.com/auth0-samples/auth0-react-samples/issues/145). - ## Why do I get `auth0_spa_js_1.default is not a function` when using Typescript? If you're hitting this issue, set `esModuleInterop: true` in your `tsconfig.json` file (inside `compilerOptions`). Due to how the type system works in Typescript, if one of your dependencies uses `allowSyntheticDefaultImports: true`, then all the consumers of that dependency must use `allowSyntheticDefaultImports: true` as well. This, of course, is not ideal and might break your app if you depend on this setting being `false`. The Typescript team added the `esModuleInterop` flag that helps in this scenario. - ## Why do I get `auth0-spa-js must run on a secure origin`? Internally, the SDK uses [Web Cryptography API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) to create [SHA-256 digest](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest). According to the spec ([via Github issues](https://github.com/w3c/webcrypto/issues/28)), Web Cryptography API requires a secure origin, so that accessing `Crypto.subtle` in a not secure context return undefined. -In most browsers, secure origins are origins that match at least one of the following (scheme, host, port) patterns: +In most browsers, secure origins are origins that match at least one of the following (scheme, host, port) patterns: ``` (https, *, *) @@ -86,4 +84,6 @@ In most browsers, secure origins are origins that match at least one of the foll (*, 127/8, *) (*, ::1/128, *) (file, *, —) -``` \ No newline at end of file +``` + +If you're running your application from a secure origin, it's possible that your browser doesn't support the Web Crypto API. For a compatibility table, please check https://caniuse.com/#feat=mdn-api_subtlecrypto diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 493d921da..39e36796c 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -105,44 +105,9 @@ describe('Auth0', () => { }); expect(auth0).toBeInstanceOf(Auth0Client); }); - it('should use msCrypto when available', async () => { - (global).crypto = undefined; - (global).msCrypto = { subtle: 'ms' }; - - const auth0 = await createAuth0Client({ - domain: TEST_DOMAIN, - client_id: TEST_CLIENT_ID - }); - expect(auth0).toBeDefined(); - expect((global).crypto.subtle).toBe('ms'); - }); - - it('should return, logging a warning if crypto.digest is undefined', async () => { - (global).crypto = {}; - - await expect( - createAuth0Client({ - domain: TEST_DOMAIN, - client_id: TEST_CLIENT_ID - }) - ).rejects.toThrowError(` - auth0-spa-js must run on a secure origin. - See https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#why-do-i-get-error-invalid-state-in-firefox-when-refreshing-the-page-immediately-after-a-login - for more information. - `); - }); - it('should return, logging a warning if crypto is unavailable', async () => { - (global).crypto = undefined; - (global).msCrypto = undefined; - - await expect( - createAuth0Client({ - domain: TEST_DOMAIN, - client_id: TEST_CLIENT_ID - }) - ).rejects.toThrowError( - 'For security reasons, `window.crypto` is required to run `auth0-spa-js`.' - ); + it('should call `utils.validateCrypto`', async () => { + const { utils } = await setup(); + expect(utils.validateCrypto).toHaveBeenCalled(); }); }); describe('loginWithPopup()', () => { diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index ae782b4e9..c08aa63d7 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -11,7 +11,10 @@ import { openPopup, runPopup, runIframe, - urlDecodeB64 + urlDecodeB64, + getCrypto, + getCryptoSubtle, + validateCrypto } from '../src/utils'; import { DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS } from '../src/constants'; @@ -495,4 +498,53 @@ describe('utils', () => { jest.useRealTimers(); }); }); + describe('getCrypto', () => { + it('should use msCrypto when window.crypto is unavailable', () => { + (global).crypto = undefined; + (global).msCrypto = 'ms'; + + const theCrypto = getCrypto(); + expect(theCrypto).toBe('ms'); + }); + it('should use window.crypto when available', () => { + (global).crypto = 'window'; + (global).msCrypto = 'ms'; + + const theCrypto = getCrypto(); + expect(theCrypto).toBe('window'); + }); + }); + describe('getCryptoSubtle', () => { + it('should use crypto.webkitSubtle when available', () => { + (global).crypto = { subtle: undefined, webkitSubtle: 'webkit' }; + + const theSubtle = getCryptoSubtle(); + expect(theSubtle).toBe('webkit'); + }); + it('should use crypto.subtle when available', () => { + (global).crypto = { subtle: 'window', webkitSubtle: 'webkit' }; + + const theSubtle = getCryptoSubtle(); + expect(theSubtle).toBe('window'); + }); + }); + describe('validateCrypto', () => { + it('should throw error if crypto is unavailable', () => { + (global).crypto = undefined; + (global).msCrypto = undefined; + + expect(validateCrypto).toThrowError( + 'For security reasons, `window.crypto` is required to run `auth0-spa-js`.' + ); + }); + it('should throw error if crypto.subtle is undefined', () => { + (global).crypto = {}; + + expect(validateCrypto).toThrowError(` + auth0-spa-js must run on a secure origin. + See https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#why-do-i-get-auth0-spa-js-must-run-on-a-secure-origin + for more information. + `); + }); + }); }); diff --git a/src/index.ts b/src/index.ts index 44a6d5018..136c82ab0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,23 +10,10 @@ import * as ClientStorage from './storage'; //this is necessary to export the type definitions used in this file import './global'; +import { validateCrypto } from './utils'; export default async function createAuth0Client(options: Auth0ClientOptions) { - if (!window.crypto && (window).msCrypto) { - (window).crypto = (window).msCrypto; - } - if (!window.crypto) { - throw new Error( - 'For security reasons, `window.crypto` is required to run `auth0-spa-js`.' - ); - } - if (typeof window.crypto.subtle === 'undefined') { - throw new Error(` - auth0-spa-js must run on a secure origin. - See https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#why-do-i-get-error-invalid-state-in-firefox-when-refreshing-the-page-immediately-after-a-login - for more information. - `); - } + validateCrypto(); const auth0 = new Auth0Client(options); diff --git a/src/utils.ts b/src/utils.ts index cc73c84ad..8943ae802 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -93,7 +93,9 @@ export const createRandomString = () => { const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.'; let random = ''; - const randomValues = Array.from(crypto.getRandomValues(new Uint8Array(43))); + const randomValues = Array.from( + getCrypto().getRandomValues(new Uint8Array(43)) + ); randomValues.forEach(v => (random += charset[v % charset.length])); return random; }; @@ -110,10 +112,7 @@ export const createQueryParams = (params: any) => { export const sha256 = async (s: string) => { const response = await Promise.resolve( - window.crypto.subtle.digest( - { name: 'SHA-256' }, - new TextEncoder().encode(s) - ) + getCryptoSubtle().digest({ name: 'SHA-256' }, new TextEncoder().encode(s)) ); // msCrypto (IE11) uses the old spec, which is not Promise based // https://msdn.microsoft.com/en-us/expression/dn904640(v=vs.71) @@ -177,3 +176,28 @@ export const oauthToken = async ({ baseUrl, ...options }: OAuthTokenOptions) => 'Content-type': 'application/json' } }); + +export const getCrypto = () => { + //ie 11.x uses msCrypto + return (window.crypto || (window).msCrypto); +}; + +export const getCryptoSubtle = () => { + //safari 10.x uses webkitSubtle + return window.crypto.subtle || (window.crypto).webkitSubtle; +}; + +export const validateCrypto = () => { + if (!getCrypto()) { + throw new Error( + 'For security reasons, `window.crypto` is required to run `auth0-spa-js`.' + ); + } + if (typeof getCryptoSubtle() === 'undefined') { + throw new Error(` + auth0-spa-js must run on a secure origin. + See https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#why-do-i-get-auth0-spa-js-must-run-on-a-secure-origin + for more information. + `); + } +}; diff --git a/static/index.html b/static/index.html index 22b17e27d..4bca8eca8 100644 --- a/static/index.html +++ b/static/index.html @@ -27,88 +27,92 @@ createAuth0Client({ domain: 'auth.brucke.club', client_id: 'wLSIP47wM39wKdDmOj6Zb5eSEw3JVhVp' - }).then(function(auth0) { - window.auth0 = auth0; - $('#login_popup').click(function() { - auth0 - .loginWithPopup({ - redirect_uri: 'http://localhost:3000/callback.html' - }) - .then(function() { - auth0.getTokenSilently().then(function(token) { - console.log(token); - }); - auth0.getUser().then(function(user) { - console.log(user); + }) + .then(function(auth0) { + window.auth0 = auth0; + $('#login_popup').click(function() { + auth0 + .loginWithPopup({ + redirect_uri: 'http://localhost:3000/callback.html' + }) + .then(function() { + auth0.getTokenSilently().then(function(token) { + console.log(token); + }); + auth0.getUser().then(function(user) { + console.log(user); + }); }); + }); + $('#login_redirect').click(function() { + auth0.loginWithRedirect({ + redirect_uri: 'http://localhost:3000/' }); - }); - $('#login_redirect').click(function() { - auth0.loginWithRedirect({ - redirect_uri: 'http://localhost:3000/' }); - }); - $('#login_redirect_callback').click(function() { - auth0.handleRedirectCallback().then(function() { - window.history.replaceState( - {}, - document.title, - window.location.origin + '/' - ); + $('#login_redirect_callback').click(function() { + auth0.handleRedirectCallback().then(function() { + window.history.replaceState( + {}, + document.title, + window.location.origin + '/' + ); + }); }); - }); - $('#getToken').click(function() { - auth0.getTokenSilently().then(function(token) { - alert(token); + $('#getToken').click(function() { + auth0.getTokenSilently().then(function(token) { + alert(token); + }); }); - }); - $('#getTokenPopup').click(function() { - auth0 - .getTokenWithPopup({ - audience: 'https://brucke.auth0.com/api/v2/', - scope: 'read:rules' - }) - .then(function(token) { - console.log(token); + $('#getTokenPopup').click(function() { + auth0 + .getTokenWithPopup({ + audience: 'https://brucke.auth0.com/api/v2/', + scope: 'read:rules' + }) + .then(function(token) { + console.log(token); + }); + }); + $('#getUser').click(function() { + auth0.getUser().then(function(user) { + alert(JSON.stringify(user, null, 1)); }); - }); - $('#getUser').click(function() { - auth0.getUser().then(function(user) { - alert(JSON.stringify(user, null, 1)); }); - }); - $('#getIdTokenClaims').click(function() { - auth0.getIdTokenClaims().then(function(claims) { - console.log(claims); - //if you need the raw id_token, you can access it in the __raw property - const id_token = claims.__raw; + $('#getIdTokenClaims').click(function() { + auth0.getIdTokenClaims().then(function(claims) { + console.log(claims); + //if you need the raw id_token, you can access it in the __raw property + const id_token = claims.__raw; + }); }); - }); - $('#getToken_audience').click(function() { - var differentAudienceOptions = { - audience: 'https://brucke.auth0.com/api/v2/', - scope: 'read:rules', - redirect_uri: 'http://localhost:3000/callback.html' - }; - auth0 - .getTokenSilently(differentAudienceOptions) - .then(function(token) { - alert(token); + $('#getToken_audience').click(function() { + var differentAudienceOptions = { + audience: 'https://brucke.auth0.com/api/v2/', + scope: 'read:rules', + redirect_uri: 'http://localhost:3000/callback.html' + }; + auth0 + .getTokenSilently(differentAudienceOptions) + .then(function(token) { + alert(token); + }); + }); + $('#logout').click(function() { + auth0.logout({ + returnTo: 'http://localhost:3000/' }); - }); - $('#logout').click(function() { - auth0.logout({ - returnTo: 'http://localhost:3000/' }); - }); - $('#logout-no-clientid').click(function() { - auth0.logout({ - client_id: null, - returnTo: 'http://localhost:3000/' + $('#logout-no-clientid').click(function() { + auth0.logout({ + client_id: null, + returnTo: 'http://localhost:3000/' + }); }); + }) + .catch(function(err) { + console.error(err); }); - }); });