Skip to content

Commit

Permalink
[SDK-1179] Support for rotating refresh tokens (#315)
Browse files Browse the repository at this point in the history
* Refactored getting token using iframe into its own method

* Implemented getTokenUsingRefreshToken

* Fixed up the playground page to support refresh tokens

* Set offline_access scope during initialization

* Added error condition for when a refresh token isn't stored or no cache exists

* Removed specification of audience when calling token endpoint

* Clarified docs on useRefreshTokens

* Simplified usage of getUniqueScopes in index.ts

* Fixed some playground syntax issues for IE11

* Playground now shows auth info on load if authenticated

* Simplified integration tests

* Added more integration tests around getting access tokens

* Encoded the nonce value when building authorize URLs

* Renamed encodeState to encode

* Fixed broken integration test
  • Loading branch information
Steve Hobbs authored Jan 8, 2020
1 parent c1d5f19 commit 5ece8d2
Show file tree
Hide file tree
Showing 11 changed files with 810 additions and 314 deletions.
501 changes: 385 additions & 116 deletions __tests__/index.test.ts

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions __tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
createQueryParams,
bufferToBase64UrlEncoded,
createRandomString,
encodeState,
decodeState,
encode,
decode,
sha256,
openPopup,
runPopup,
Expand Down Expand Up @@ -138,14 +138,14 @@ describe('utils', () => {
expect(result.length).toBeLessThanOrEqual(128);
});
});
describe('encodeState', () => {
describe('encode', () => {
it('encodes state', () => {
expect(encodeState('test')).toBe('dGVzdA==');
expect(encode('test')).toBe('dGVzdA==');
});
});
describe('decodeState', () => {
describe('decode', () => {
it('decodes state', () => {
expect(decodeState('dGVzdA==')).toBe('test');
expect(decode('dGVzdA==')).toBe('test');
});
});
describe('sha256', () => {
Expand Down Expand Up @@ -270,14 +270,16 @@ describe('utils', () => {
)
);
await oauthToken({
grant_type: 'authorization_code',
baseUrl: 'https://test.com',
client_id: 'client_idIn',
code: 'codeIn',
code_verifier: 'code_verifierIn'
});

expect(mockUnfetch).toHaveBeenCalledWith('https://test.com/oauth/token', {
body:
'{"grant_type":"authorization_code","redirect_uri":"http://localhost","client_id":"client_idIn","code":"codeIn","code_verifier":"code_verifierIn"}',
'{"redirect_uri":"http://localhost","grant_type":"authorization_code","client_id":"client_idIn","code":"codeIn","code_verifier":"code_verifierIn"}',
headers: { 'Content-type': 'application/json' },
method: 'POST'
});
Expand Down
132 changes: 72 additions & 60 deletions cypress/integration/getTokenSilently.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {

describe('getTokenSilently', function() {
beforeEach(cy.resetTests);
afterEach(cy.logout);

it('return error when not logged in', function(done) {
it('returns an error when not logged in', function(done) {
whenReady().then(win =>
win.auth0.getTokenSilently().catch(error => {
shouldBe('login_required', error.error);
Expand All @@ -19,73 +20,84 @@ describe('getTokenSilently', function() {
);
});

it.skip('Builds URL correctly', function(done) {
cy.login().then(() => {
whenReady().then(win => {
var iframe = win.document.createElement('iframe');
cy.stub(win.document, 'createElement', type =>
type === 'iframe' ? iframe : window.document.createElement
);
return win.auth0.getTokenSilently().then(() => {
const parsedUrl = new URL(iframe.src);
shouldBe(parsedUrl.host, 'brucke.auth0.com');
const pageParams = decode(parsedUrl.search.substr(1));
shouldBeUndefined(pageParams.code_verifier);
shouldNotBeUndefined(pageParams.code_challenge);
shouldNotBeUndefined(pageParams.code_challenge_method);
shouldNotBeUndefined(pageParams.state);
shouldNotBeUndefined(pageParams.nonce);
shouldBe(pageParams.redirect_uri, win.location.origin);
shouldBe(pageParams.response_mode, 'web_message');
shouldBe(pageParams.response_type, 'code');
shouldBe(pageParams.scope, 'openid profile email');
shouldBe(pageParams.client_id, 'wLSIP47wM39wKdDmOj6Zb5eSEw3JVhVp');
done();
describe('when using an iframe', () => {
describe('using an in-memory store', () => {
it('gets a new access token', () => {
return whenReady().then(win => {
cy.login().then(() => {
cy.get('[data-cy=get-token]').click();
cy.get('[data-cy=access-token]').should('have.length', 2); // 1 from handleRedirectCallback, 1 from clicking "Get access token"
cy.get('[data-cy=error]').should('not.exist');
});
});
});
});
});

it('return cached token after login', function(done) {
cy.login().then(() => {
whenReady().then(win =>
win.auth0.getTokenSilently().then(token => {
shouldNotBeUndefined(token);
win.auth0.getTokenSilently().then(token2 => {
shouldNotBeUndefined(token2);
shouldBe(token, token2);
done();
it('can get the access token after refreshing the page', () => {
return whenReady().then(win => {
cy.login().then(() => {
cy.reload();

cy.get('[data-cy=get-token]')
.click()
.wait(500)
.get('[data-cy=access-token]')
.should('have.length', 1);

cy.get('[data-cy=error]').should('not.exist');
});
})
);
});
});
});
});

it('ignores cache if `ignoreCache:true`', function(done) {
cy.login().then(() => {
whenReady().then(win =>
win.auth0.getTokenSilently().then(token => {
shouldNotBeUndefined(token);
win.auth0.getTokenSilently({ ignoreCache: true }).then(token2 => {
shouldNotBeUndefined(token2);
shouldNotBe(token, token2);
done();
describe('using local storage', () => {
it('can get the access token after refreshing the page', () => {
return whenReady().then(win => {
cy.toggleSwitch('local-storage');

cy.login().then(() => {
cy.reload();

cy.get('[data-cy=get-token]')
.click()
.wait(500)
.get('[data-cy=access-token]')
.should('have.length', 1)
.then(() => {
expect(
win.localStorage.getItem(
'@@auth0spajs@@::wLSIP47wM39wKdDmOj6Zb5eSEw3JVhVp::default::openid profile email'
)
).to.not.be.null;
});

cy.get('[data-cy=error]').should('not.exist');
});
})
);
});
});
});
});

describe('when using refresh tokens', () => {
it('displays an error when trying to get an access token when the RT is missing', () => {
return whenReady().then(win => {
cy.toggleSwitch('local-storage');
cy.toggleSwitch('use-cache');

cy.login().then(() => {
cy.reload();

it('returns consent_required when using an audience without consent', function(done) {
cy.login().then(() => {
whenReady().then(win =>
win.auth0
.getTokenSilently({ audience: 'https://brucke.auth0.com/api/v2/' })
.catch(error => {
shouldBe('consent_required', error.error);
done();
})
);
cy.toggleSwitch('refresh-tokens').wait(500);

cy.get('[data-cy=get-token]')
.click()
.wait(500);

cy.get('[data-cy=error]').should(
'contain',
'No refresh token is available to fetch a new access token'
);
});
});
});
});
});
});
});
20 changes: 19 additions & 1 deletion cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,36 @@ import { whenReady } from './utils';
//
//
// -- This is a parent command --

Cypress.Commands.add('login', () => {
cy.get('#login_redirect').click();

cy.get('.auth0-lock-input-username .auth0-lock-input')
.clear()
.type('[email protected]');

cy.get('.auth0-lock-input-password .auth0-lock-input')
.clear()
.type('1234');
cy.get('.auth0-lock-submit').click();
return whenReady().then(win => win.auth0.handleRedirectCallback());

return whenReady().then(() => {
cy.get('#handle_redirect_callback').click();
return cy.wait(250);
});
});

Cypress.Commands.add('handleRedirectCallback', () => {
cy.get('#handle_redirect_callback').click();
return cy.wait(250);
});

Cypress.Commands.add('logout', () => cy.get('[data-cy=logout]').click());

Cypress.Commands.add('toggleSwitch', name =>
cy.get(`[data-cy=switch-${name}]`).click()
);

Cypress.Commands.add('loginNoCallback', () => {
cy.get('#login_redirect').click();

Expand All @@ -39,5 +56,6 @@ Cypress.Commands.add('loginNoCallback', () => {

Cypress.Commands.add('resetTests', () => {
cy.visit('http://localhost:3000');
cy.get('#reset-config').click();
cy.get('#logout').click();
});
Loading

0 comments on commit 5ece8d2

Please sign in to comment.