Skip to content
This repository has been archived by the owner on Nov 25, 2020. It is now read-only.

Commit

Permalink
fix(authService): listen to storage events. fixes login/logout in oth…
Browse files Browse the repository at this point in the history
…er tabs

NEW: storageChangedRedirect 0 redirect automatically after storage events if needed
BREAKING CHANGE: hasDataAnalyzed renamed to responseAnalyzed
  • Loading branch information
doktordirk committed Aug 18, 2016
1 parent e2ef686 commit 52c2f67
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 64 deletions.
7 changes: 7 additions & 0 deletions doc/Quick start.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export function configure(aurelia) {
}
```

### Getting the current authentication status

There are two options:

* authService.isAuthenticated(): This function gets the current token on each call from the window storage to calucate the current authentication status. Since it's a function, Aurelia will use dirty checking, if you bind to it.
* authService.authenticated: This property gets updated by timeout and storage events to keep it accurate all the time. No dirty-checking is needed, but you might not like that there is magic used to keep it updated.

### Provide a UI for a login, signup and profile

Button actions are passed to the corresponding view model via a simple click.delegate:
Expand Down
6 changes: 3 additions & 3 deletions doc/api_authService.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ import {AuthService} from 'aurelia-authentication';

### .authenticated

| Type | Description |
| ------- | ------------------------------------------------ |
| Boolean | Authentication status as set by timeout function |
| Type | Description |
| ------- | ------------------------------------------- |
| Boolean | Automatically updated authentication status |

----------

Expand Down
6 changes: 4 additions & 2 deletions doc/baseConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ loginRoute = '/login';
loginOnSignup = true;
// If loginOnSignup == false: The SPA url to which the user is redirected after a successful signup (else loginRedirect is used)
signupRedirect = '#/login';
// redirect when token expires. 0 = don't redirect (default), 1 = use logoutRedirect, string = redirect there
expiredRedirect = 0;
// reload page when token expires. 0 = don't reload (default), 1 = do reload page
expiredReload = 0;
// reload page when storage changed aka login/logout in other tabs/windows. 0 = don't reload (default), 1 = do reload page
storageChangedReload = 0;


// API related options
Expand Down
72 changes: 58 additions & 14 deletions src/authService.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class AuthService {

// get token stored in previous format over
const oldStorageKey = config.tokenPrefix
? config.tokenPrefix + '_' + config.tokenName
? `${config.tokenPrefix}_${config.tokenName}`
: config.tokenName;
const oldToken = authentication.storage.get(oldStorageKey);

Expand All @@ -67,8 +67,33 @@ export class AuthService {

// initialize status by resetting if existing stored responseObject
this.setResponseObject(this.authentication.getResponseObject());

// listen to storage events in case the user logs in or out in another tab/window
PLATFORM.addEventListener('storage', this.storageEventHandler);
}

/**
* The handler used for storage events. Detects and handles authentication changes in other tabs/windows
*
* @param {StorageEvent}
*/
storageEventHandler = event => {
if (event.key !== this.config.storageKey) {
return;
}

LogManager.getLogger('authentication').info('Stored token changed event');

let wasAuthenticated = this.authenticated;
this.authentication.responseAnalyzed = false;
this.updateAuthenticated();

if (this.config.storageChangedRedirect && wasAuthenticated !== this.authenticated) {
PLATFORM.location.assign(this.config.storageChangedRedirect);
}
}


/**
* Getter: The configured client for all aurelia-authentication requests
*
Expand All @@ -78,6 +103,12 @@ export class AuthService {
return this.config.client;
}

/**
* Getter: The authentication class instance
*
* @return {boolean}
* @deprecated
*/
get auth() {
LogManager.getLogger('authentication').warn('AuthService.auth is deprecated. Use .authentication instead.');
return this.authentication;
Expand All @@ -96,8 +127,14 @@ export class AuthService {
&& this.authentication.getAccessToken()
&& this.authentication.getRefreshToken()) {
this.updateToken();
} else {
this.logout(this.config.expiredRedirect);

return;
}

this.setResponseObject(null);

if (this.config.expiredRedirect) {
PLATFORM.location.assign(this.config.expiredRedirect);
}
}, ttl);
}
Expand All @@ -118,10 +155,17 @@ export class AuthService {
* @param {Object} response The servers response as GOJO
*/
setResponseObject(response) {
this.clearTimeout();

this.authentication.setResponseObject(response);

this.updateAuthenticated();
}

/**
* Update authenticated. Sets login status and timeout
*/
updateAuthenticated() {
this.clearTimeout();

let wasAuthenticated = this.authenticated;
this.authenticated = this.authentication.isAuthenticated();

Expand Down Expand Up @@ -202,12 +246,12 @@ export class AuthService {
}

/**
* Gets authentication status
* Gets authentication status from storage
*
* @returns {Boolean} For Non-JWT and unexpired JWT: true, else: false
*/
isAuthenticated() {
this.authentication.hasTokenAnalyzed = false;
this.authentication.responseAnalyzed = false;

let authenticated = this.authentication.isAuthenticated();

Expand Down Expand Up @@ -307,8 +351,8 @@ export class AuthService {
let content;

if (typeof arguments[0] === 'object') {
content = arguments[0];
options = arguments[1];
content = arguments[0];
options = arguments[1];
redirectUri = arguments[2];
} else {
content = {
Expand Down Expand Up @@ -342,9 +386,9 @@ export class AuthService {
let content;

if (typeof arguments[0] === 'object') {
content = arguments[0];
content = arguments[0];
optionsOrRedirectUri = arguments[1];
redirectUri = arguments[2];
redirectUri = arguments[2];
} else {
content = {
'email': emailOrCredentials,
Expand All @@ -370,15 +414,15 @@ export class AuthService {
/**
* logout locally and redirect to redirectUri (if set) or redirectUri of config. Sends logout request first, if set in config
*
* @param {[String]} [redirectUri] [optional redirectUri overwrite]
* @param {[String]} [redirectUri] [optional redirectUri overwrite]
*
* @return {Promise<>|Promise<Object>|Promise<Error>} Server response as Object
*/
logout(redirectUri) {
logout(redirectUri, query) {
let localLogout = response => new Promise(resolve => {
this.setResponseObject(null);

this.authentication.redirect(redirectUri, this.config.logoutRedirect);
this.authentication.redirect(redirectUri, this.config.logoutRedirect, query);

if (typeof this.onLogout === 'function') {
this.onLogout(response);
Expand Down
23 changes: 12 additions & 11 deletions src/authentication.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {PLATFORM} from 'aurelia-pal';
import {buildQueryString} from 'aurelia-path';
import {inject} from 'aurelia-dependency-injection';
import {deprecated} from 'aurelia-metadata';
import jwtDecode from 'jwt-decode';
Expand All @@ -23,7 +24,7 @@ export class Authentication {
this.idToken = null;
this.payload = null;
this.exp = null;
this.hasTokenAnalyzed = false;
this.responseAnalyzed = false;
}


Expand Down Expand Up @@ -86,7 +87,7 @@ export class Authentication {
this.idToken = null;
this.payload = null;
this.exp = null;
this.hasTokenAnalyzed = false;
this.responseAnalyzed = false;

this.storage.remove(this.config.storageKey);
}
Expand All @@ -95,27 +96,27 @@ export class Authentication {
/* get data, update if needed first */

getAccessToken() {
if (!this.hasTokenAnalyzed) this.getDataFromResponse(this.getResponseObject());
if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject());
return this.accessToken;
}

getRefreshToken() {
if (!this.hasTokenAnalyzed) this.getDataFromResponse(this.getResponseObject());
if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject());
return this.refreshToken;
}

getIdToken() {
if (!this.hasTokenAnalyzed) this.getDataFromResponse(this.getResponseObject());
if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject());
return this.idToken;
}

getPayload() {
if (!this.hasTokenAnalyzed) this.getDataFromResponse(this.getResponseObject());
if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject());
return this.payload;
}

getExp() {
if (!this.hasTokenAnalyzed) this.getDataFromResponse(this.getResponseObject());
if (!this.responseAnalyzed) this.getDataFromResponse(this.getResponseObject());
return this.exp;
}

Expand Down Expand Up @@ -169,7 +170,7 @@ export class Authentication {

this.exp = this.payload ? parseInt(this.payload.exp, 10) : NaN;

this.hasTokenAnalyzed = true;
this.responseAnalyzed = true;

return {
accessToken: this.accessToken,
Expand Down Expand Up @@ -243,7 +244,7 @@ export class Authentication {
return providerLogin.open(this.config.providers[name], userData);
}

redirect(redirectUrl, defaultRedirectUrl) {
redirect(redirectUrl, defaultRedirectUrl, query) {
// stupid rule to keep it BC
if (redirectUrl === true) {
LogManager.getLogger('authentication').warn('DEPRECATED: Setting redirectUrl === true to actually *not redirect* is deprecated. Set redirectUrl === 0 instead.');
Expand All @@ -258,9 +259,9 @@ export class Authentication {
return;
}
if (typeof redirectUrl === 'string') {
PLATFORM.location.href = encodeURI(redirectUrl);
PLATFORM.location.href = encodeURI(redirectUrl + (query ? `?${buildQueryString(query)}` : ''));
} else if (defaultRedirectUrl) {
PLATFORM.location.href = defaultRedirectUrl;
PLATFORM.location.href = defaultRedirectUrl + (query ? `?${buildQueryString(query)}` : '');
}
}
}
47 changes: 42 additions & 5 deletions src/baseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ export class BaseConfig {
loginOnSignup = true;
// If loginOnSignup == false: The SPA url to which the user is redirected after a successful signup (else loginRedirect is used)
signupRedirect = '#/login';
// redirect when token expires. 0 = don't redirect (default), 1 = use logoutRedirect, string = redirect there
expiredRedirect = 0;

// The SPA url to load when the token expires
expiredRedirect = '#/';
// The SPA url to load when the authentication status changed in other tabs/windows (detected through storageEvents)
storageChangedRedirect = '#/';

// API related options
// ===================
Expand Down Expand Up @@ -284,13 +285,31 @@ export class BaseConfig {
};

/* deprecated defaults */
/**
* @deprecated
*/
_authToken = 'Bearer';
/**
* @deprecated
*/
_responseTokenProp = 'access_token';
/**
* @deprecated
*/
_tokenName = 'token';
/**
* @deprecated
*/
_tokenRoot = false;
/**
* @deprecated
*/
_tokenPrefix = 'aurelia';

/* deprecated methods and parameteres */
/**
* @deprecated
*/
set authToken(authToken) {
LogManager.getLogger('authentication').warn('BaseConfig.authToken is deprecated. Use BaseConfig.authTokenType instead.');
this._authTokenType = authToken;
Expand All @@ -301,6 +320,9 @@ export class BaseConfig {
return this._authTokenType;
}

/**
* @deprecated
*/
set responseTokenProp(responseTokenProp) {
LogManager.getLogger('authentication').warn('BaseConfig.responseTokenProp is deprecated. Use BaseConfig.accessTokenProp instead.');
this._responseTokenProp = responseTokenProp;
Expand All @@ -311,6 +333,9 @@ export class BaseConfig {
return this._responseTokenProp;
}

/**
* @deprecated
*/
set tokenRoot(tokenRoot) {
LogManager.getLogger('authentication').warn('BaseConfig.tokenRoot is deprecated. Use BaseConfig.accessTokenRoot instead.');
this._tokenRoot = tokenRoot;
Expand All @@ -321,6 +346,9 @@ export class BaseConfig {
return this._tokenRoot;
}

/**
* @deprecated
*/
set tokenName(tokenName) {
LogManager.getLogger('authentication').warn('BaseConfig.tokenName is deprecated. Use BaseConfig.accessTokenName instead.');
this._tokenName = tokenName;
Expand All @@ -331,6 +359,9 @@ export class BaseConfig {
return this._tokenName;
}

/**
* @deprecated
*/
set tokenPrefix(tokenPrefix) {
LogManager.getLogger('authentication').warn('BaseConfig.tokenPrefix is obsolete. Use BaseConfig.storageKey instead.');
this._tokenPrefix = tokenPrefix;
Expand All @@ -340,20 +371,26 @@ export class BaseConfig {
return this._tokenPrefix || 'aurelia';
}

/**
* @deprecated
*/
get current() {
LogManager.getLogger('authentication').warn('Getter BaseConfig.current is deprecated. Use BaseConfig directly instead.');
return this;
}
set current(_) {
throw new Error('Setter BaseConfig.current is obsolete. Use BaseConfig directly instead.');
throw new Error('Setter BaseConfig.current has been removed. Use BaseConfig directly instead.');
}

/**
* @deprecated
*/
get _current() {
LogManager.getLogger('authentication').warn('Getter BaseConfig._current is deprecated. Use BaseConfig directly instead.');
return this;
}
set _current(_) {
throw new Error('Setter BaseConfig._current is obsolete. Use BaseConfig directly instead.');
throw new Error('Setter BaseConfig._current has been removed. Use BaseConfig directly instead.');
}
}

Expand Down
Loading

0 comments on commit 52c2f67

Please sign in to comment.