Skip to content

Commit

Permalink
fix: PKCE isSupported method
Browse files Browse the repository at this point in the history
- Check for TextEncoder
- Provider more specific error messages
  • Loading branch information
aarongranick-okta committed Nov 15, 2019
1 parent 4f67efa commit 7bf987a
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ var authClient = new OktaAuth(config);

##### PKCE OAuth 2.0 flow

By default the `implicit` OAuth flow will be used. It is widely supported by most browsers. PKCE is a newer flow which is more secure, but does require certain capabilities from the browser.
By default the `implicit` OAuth flow will be used. It is widely supported by most browsers. PKCE is a newer flow which is more secure, but does require certain capabilities from the browser. Specifically, the browser must implement `crypto.subtle` (also known as `webcrypto`). [Most modern browsers provide this](https://caniuse.com/#feat=cryptography) when running in a secure context (on an HTTPS connection). PKCE also requires the [TextEncoder](https://caniuse.com/#feat=textencoder) object. This is available on all major platforms except IE Edge. In this case, we recommend using a polyfill/shim such as [text-encoding](https://www.npmjs.com/package/text-encoding).

To use PKCE flow, set `pkce` to `true` in your config.

Expand Down
19 changes: 17 additions & 2 deletions packages/okta-auth-js/lib/browser/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ function OktaAuthBuilder(args) {
};

if (this.options.pkce && !sdk.features.isPKCESupported()) {
throw new AuthSdkError('This browser doesn\'t support PKCE');
var errorMessage = 'PKCE requires a modern browser with encryption support running in a secure context.';
if (!sdk.features.isHTTPS()) {
errorMessage += '\nThe current page is not being served with HTTPS protocol. Try using HTTPS.';
}
if (!sdk.features.hasTextEncoder()) {
errorMessage += '\n"TextEncoder" is not defined. You may need a polyfill/shim for this browser.';
}
throw new AuthSdkError(errorMessage);
}

this.userAgent = 'okta-auth-js-' + SDK_VERSION;
Expand Down Expand Up @@ -162,8 +169,16 @@ proto.features.isTokenVerifySupported = function() {
return typeof crypto !== 'undefined' && crypto.subtle && typeof Uint8Array !== 'undefined';
};

proto.features.hasTextEncoder = function() {
return typeof TextEncoder !== 'undefined';
};

proto.features.isPKCESupported = function() {
return proto.features.isTokenVerifySupported();
return proto.features.isTokenVerifySupported() && proto.features.hasTextEncoder();
};

proto.features.isHTTPS = function() {
return window.location.protocol === 'https:';
};

// { username, password, (relayState), (context) }
Expand Down
79 changes: 78 additions & 1 deletion packages/okta-auth-js/test/spec/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,24 @@ describe('features', function() {
});
});

describe('hasTextEncoder', function() {
it('returns true if TextEncoder is defined', function() {
window.TextEncoder = true;
expect(OktaAuth.features.hasTextEncoder()).toBe(true);
});
it('returns false if TextEncoder is undefined', function() {
window.TextEncoder = undefined;
expect(OktaAuth.features.hasTextEncoder()).toBe(false);
});
});

describe('isPKCESupported', function() {
beforeEach(function() {
window.crypto = {
subtle: true
};
window.Uint8Array = true;
window.TextEncoder = true;
});

it('can succeed', function() {
Expand All @@ -87,10 +98,72 @@ describe('features', function() {
expect(OktaAuth.features.isPKCESupported()).toBe(false);
});

it('fails if no TextEncoder', function() {
window.TextEncoder = undefined;
expect(OktaAuth.features.isPKCESupported()).toBe(false);
});

it('throw an error during construction if pkce is true and PKCE is not supported', function () {
var err;
spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(false);
spyOn(OktaAuth.features, 'isHTTPS').and.returnValue(true);
try {
new OktaAuth({
url: 'https://dev-12345.oktapreview.com',
pkce: true,
});
} catch (e) {
err = e;
}
expect(err.name).toEqual('AuthSdkError');
expect(err.errorSummary).toEqual('PKCE requires a modern browser with encryption support running in a secure context.');
});


it('HTTPS: throw a more specific error', function () {
var err;
spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(false);
spyOn(OktaAuth.features, 'isHTTPS').and.returnValue(false);
try {
new OktaAuth({
url: 'https://dev-12345.oktapreview.com',
pkce: true,
});
} catch (e) {
err = e;
}
expect(err.name).toEqual('AuthSdkError');
expect(err.errorSummary).toEqual(
'PKCE requires a modern browser with encryption support running in a secure context.\n' +
'The current page is not being served with HTTPS protocol. Try using HTTPS.'
);
});

it('TextEncoder: throw a more specific error', function () {
var err;
spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(false);
spyOn(OktaAuth.features, 'isHTTPS').and.returnValue(true);
window.TextEncoder = undefined;
try {
new OktaAuth({
url: 'https://dev-12345.oktapreview.com',
pkce: true,
});
} catch (e) {
err = e;
}
expect(err.name).toEqual('AuthSdkError');
expect(err.errorSummary).toEqual(
'PKCE requires a modern browser with encryption support running in a secure context.\n' +
'"TextEncoder" is not defined. You may need a polyfill/shim for this browser.'
);
});

it('TextEncoder & HTTPS: throw a more specific error', function () {
var err;
spyOn(OktaAuth.features, 'isPKCESupported').and.returnValue(false);
spyOn(OktaAuth.features, 'isHTTPS').and.returnValue(false);
window.TextEncoder = undefined;
try {
new OktaAuth({
url: 'https://dev-12345.oktapreview.com',
Expand All @@ -100,7 +173,11 @@ describe('features', function() {
err = e;
}
expect(err.name).toEqual('AuthSdkError');
expect(err.errorSummary).toEqual('This browser doesn\'t support PKCE');
expect(err.errorSummary).toEqual(
'PKCE requires a modern browser with encryption support running in a secure context.\n' +
'The current page is not being served with HTTPS protocol. Try using HTTPS.\n' +
'"TextEncoder" is not defined. You may need a polyfill/shim for this browser.'
);
});

});
Expand Down

0 comments on commit 7bf987a

Please sign in to comment.