Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix safari10 initialization error #232

Merged
merged 8 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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