Skip to content

Commit

Permalink
Fix safari10 initialization error (#232)
Browse files Browse the repository at this point in the history
* Fix safari10 initialization error

* application

* use subtle before webkitSubtle

* abstract crypto methods
  • Loading branch information
Luís Rudge authored Oct 10, 2019
1 parent 39c768e commit 12cdc9b
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 132 deletions.
8 changes: 4 additions & 4 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, *, *)
Expand All @@ -86,4 +84,6 @@ In most browsers, secure origins are origins that match at least one of the foll
(*, 127/8, *)
(*, ::1/128, *)
(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
41 changes: 3 additions & 38 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,44 +105,9 @@ describe('Auth0', () => {
});
expect(auth0).toBeInstanceOf(Auth0Client);
});
it('should use msCrypto when available', async () => {
(<any>global).crypto = undefined;
(<any>global).msCrypto = { subtle: 'ms' };

const auth0 = await createAuth0Client({
domain: TEST_DOMAIN,
client_id: TEST_CLIENT_ID
});
expect(auth0).toBeDefined();
expect((<any>global).crypto.subtle).toBe('ms');
});

it('should return, logging a warning if crypto.digest is undefined', async () => {
(<any>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 () => {
(<any>global).crypto = undefined;
(<any>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()', () => {
Expand Down
54 changes: 53 additions & 1 deletion __tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -495,4 +498,53 @@ describe('utils', () => {
jest.useRealTimers();
});
});
describe('getCrypto', () => {
it('should use msCrypto when window.crypto is unavailable', () => {
(<any>global).crypto = undefined;
(<any>global).msCrypto = 'ms';

const theCrypto = getCrypto();
expect(theCrypto).toBe('ms');
});
it('should use window.crypto when available', () => {
(<any>global).crypto = 'window';
(<any>global).msCrypto = 'ms';

const theCrypto = getCrypto();
expect(theCrypto).toBe('window');
});
});
describe('getCryptoSubtle', () => {
it('should use crypto.webkitSubtle when available', () => {
(<any>global).crypto = { subtle: undefined, webkitSubtle: 'webkit' };

const theSubtle = getCryptoSubtle();
expect(theSubtle).toBe('webkit');
});
it('should use crypto.subtle when available', () => {
(<any>global).crypto = { subtle: 'window', webkitSubtle: 'webkit' };

const theSubtle = getCryptoSubtle();
expect(theSubtle).toBe('window');
});
});
describe('validateCrypto', () => {
it('should throw error if crypto is unavailable', () => {
(<any>global).crypto = undefined;
(<any>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', () => {
(<any>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.
`);
});
});
});
17 changes: 2 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 && (<any>window).msCrypto) {
(<any>window).crypto = (<any>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);

Expand Down
34 changes: 29 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -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)
Expand Down Expand Up @@ -177,3 +176,28 @@ export const oauthToken = async ({ baseUrl, ...options }: OAuthTokenOptions) =>
'Content-type': 'application/json'
}
});

export const getCrypto = () => {
//ie 11.x uses msCrypto
return <Crypto>(window.crypto || (<any>window).msCrypto);
};

export const getCryptoSubtle = () => {
//safari 10.x uses webkitSubtle
return window.crypto.subtle || (<any>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.
`);
}
};
142 changes: 73 additions & 69 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
</script>
</body>
Expand Down

0 comments on commit 12cdc9b

Please sign in to comment.