Skip to content

Commit

Permalink
Improve error handling, doc, add tests for TokenManager "error" event (
Browse files Browse the repository at this point in the history
…#247)

* Improve doc, add test for TokenManager "error" event

* Clear token on 'AuthSdkError'

* Add tests for AuthSdkError handling during token renew
  • Loading branch information
aarongranick-okta authored Sep 19, 2019
1 parent 7c544e9 commit 2c5d123
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 4 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1673,11 +1673,15 @@ authClient.tokenManager.on('renewed', function (key, newToken, oldToken) {

// Triggered when an OAuthError is returned via the API
authClient.tokenManager.on('error', function (err) {
console.log('TokenManager error:', err.message);
console.log('TokenManager error:', err);
// err.name
// err.message
// err.errorCode
// err.errorSummary

if (err.errorCode === 'login_required') {
// Return to unauthenticated state
}
});
```

Expand Down
2 changes: 1 addition & 1 deletion lib/TokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function renew(sdk, tokenMgmtRef, storage, key) {
return freshToken;
})
.catch(function(err) {
if (err.name === 'OAuthError') {
if (err.name === 'OAuthError' || err.name === 'AuthSdkError') {
remove(tokenMgmtRef, storage, key);
emitError(tokenMgmtRef, err);
}
Expand Down
6 changes: 6 additions & 0 deletions test/app/src/testApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ function bindFunctions(testApp, window) {
function TestApp(config) {
this.config = config;
this.oktaAuth = new OktaAuth(config);
this.oktaAuth.tokenManager.on('error', e => {
console.error('Token manager error:', e);
if (e.errorCode === 'login_required') {
this.render();
}
});
}

export default TestApp;
Expand Down
50 changes: 48 additions & 2 deletions test/karma/spec/renewToken.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ var tokens = require('../../util/tokens');
import OktaAuth from '@okta/okta-auth-js';
import oauthUtil from '../../../lib/oauthUtil';
import pkce from '../../../lib/pkce';
import OauthError from '../../../lib/errors/OAuthError';
import OAuthError from '../../../lib/errors/OAuthError';
import AuthSdkError from '../../../lib/errors/AuthSdkError';

describe('Renew token', function() {

Expand Down Expand Up @@ -79,6 +80,51 @@ describe('Renew token', function() {
});
}

it('TokenManager::renew throws an exception if token does not exist', function() {
const expectedMessage = 'The tokenManager has no token for the key: accessToken';
return bootstrap()
.then(() => {
return sdk.tokenManager.renew('accessToken');
})
.catch(e => {
expect(e instanceof AuthSdkError).toBe(true);
expect(e.message).toBe(expectedMessage);
});
});

it('TokenManager::renew emits an error if token::renew returns an OAuthError', function() {
const errorCode = 'login_required';
const errorMessage = 'The client specified not to prompt, but the user is not logged in.';
const errorCallback = jasmine.createSpy();
return bootstrap()
.then(() => {
sdk.tokenManager.on('error', errorCallback);
sdk.tokenManager.add('accessToken', ACCCESS_TOKEN_PARSED);
spyOn(oauthUtil, 'loadFrame').and.callFake(urlStr => {
const url = new URL(urlStr);
const state = url.searchParams.get('state');
var response = {
state,
error: errorCode,
error_description: errorMessage,
name: 'OAuthError'
};

// Simulate window.postMessage() from iframe
var event = new Event('message');
event.data = response;
event.origin = ISSUER;
window.dispatchEvent(event);
});
return sdk.tokenManager.renew('accessToken');
})
.catch(e => {
expect(e instanceof OAuthError).toBe(true);
expect(e.message).toBe(errorMessage);
expect(errorCallback).toHaveBeenCalledWith(e);
});
});

it('receives/throws error from iframe', function() {
// This is the error if requesting scope='offline_access'
const error = 'access_denied';
Expand Down Expand Up @@ -110,7 +156,7 @@ describe('Renew token', function() {
})
.catch(e => {
expect(oauthUtil.loadFrame).toHaveBeenCalled();
expect(e instanceof OauthError).toBe(true);
expect(e instanceof OAuthError).toBe(true);
expect(e.message).toBe(error_description);
});
});
Expand Down
165 changes: 165 additions & 0 deletions test/spec/tokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,38 @@ describe('TokenManager', function() {
});
});
});

it('removes token if an AuthSdkError is thrown while renewing', function() {
return oauthUtil.setupFrame({
authClient: setupSync(),
willFail: true,
tokenManagerAddKeys: {
'test-accessToken': tokens.standardAccessTokenParsed,
'test-idToken': tokens.standardIdTokenParsed
},
tokenManagerRenewArgs: ['test-accessToken'],
postMessageSrc: {
baseUri: 'http://obviously.fake.foo',
},
postMessageResp: {
state: oauthUtil.mockedState
}
})
.fail(function(e) {
util.expectErrorToEqual(e, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
oauthUtil.expectTokenStorageToEqual(localStorage, {
'test-idToken': tokens.standardIdTokenParsed
});
});
});
});

describe('autoRenew', function() {
Expand Down Expand Up @@ -585,6 +617,103 @@ describe('TokenManager', function() {
});
});

it('Emits an "error" event on OAuth failure', function() {
var authClient = setupSync({
tokenManager: {
autoRenew: true
}
});
var errorEventCallback = jest.fn().mockImplementation(function(err) {
util.expectErrorToEqual(err, {
name: 'OAuthError',
message: 'something went wrong',
errorCode: 'sampleErrorCode',
errorSummary: 'something went wrong'
});
})
authClient.tokenManager.on('error', errorEventCallback);

return oauthUtil.setupFrame({
authClient: authClient,
autoRenew: true,
willFail: true,
fastForwardToTime: true,
autoRenewTokenKey: 'test-idToken',
time: tokens.standardIdTokenParsed.expiresAt + 1,
tokenManagerAddKeys: {
'test-idToken': tokens.standardIdTokenParsed
},
postMessageResp: {
error: 'sampleErrorCode',
'error_description': 'something went wrong',
state: oauthUtil.mockedState
}
})
.fail(function(err) {
util.expectErrorToEqual(err, {
name: 'OAuthError',
message: 'something went wrong',
errorCode: 'sampleErrorCode',
errorSummary: 'something went wrong'
});
oauthUtil.expectTokenStorageToEqual(localStorage, {});

expect(errorEventCallback).toHaveBeenCalled();
});
});

it('Emits an "error" event on AuthSdkError', function() {
var authClient = setupSync({
tokenManager: {
autoRenew: true
}
});
var errorEventCallback = jest.fn().mockImplementation(function(err) {
util.expectErrorToEqual(err, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
})
authClient.tokenManager.on('error', errorEventCallback);

return oauthUtil.setupFrame({
authClient: authClient,
autoRenew: true,
willFail: true,
fastForwardToTime: true,
autoRenewTokenKey: 'test-idToken',
time: tokens.standardIdTokenParsed.expiresAt + 1,
tokenManagerAddKeys: {
'test-idToken': tokens.standardIdTokenParsed
},
postMessageSrc: {
baseUri: 'http://obviously.fake.foo',
},
postMessageResp: {
state: oauthUtil.mockedState
}
})
.fail(function(err) {
util.expectErrorToEqual(err, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
oauthUtil.expectTokenStorageToEqual(localStorage, {});

expect(errorEventCallback).toHaveBeenCalled();
});
});

it('removes a token on OAuth failure', function() {
return oauthUtil.setupFrame({
authClient: setupSync({
Expand Down Expand Up @@ -617,6 +746,42 @@ describe('TokenManager', function() {
});
});

it('removes a token on AuthSdkError', function() {
return oauthUtil.setupFrame({
authClient: setupSync({
tokenManager: {
autoRenew: true
}
}),
autoRenew: true,
willFail: true,
fastForwardToTime: true,
autoRenewTokenKey: 'test-idToken',
time: tokens.standardIdTokenParsed.expiresAt + 1,
tokenManagerAddKeys: {
'test-idToken': tokens.standardIdTokenParsed
},
postMessageSrc: {
baseUri: 'http://obviously.fake.foo',
},
postMessageResp: {
state: oauthUtil.mockedState
}
})
.fail(function(e) {
util.expectErrorToEqual(e, {
name: 'AuthSdkError',
message: 'The request does not match client configuration',
errorCode: 'INTERNAL',
errorSummary: 'The request does not match client configuration',
errorLink: 'INTERNAL',
errorId: 'INTERNAL',
errorCauses: [],
});
oauthUtil.expectTokenStorageToEqual(localStorage, {});
});
});

it('does not renew the token if the token has not expired', function() {
var CURRENT_TIME = 0;
var EXPIRATION_TIME = CURRENT_TIME + 10;
Expand Down

0 comments on commit 2c5d123

Please sign in to comment.