Skip to content

Commit

Permalink
Force expiries to happen 10 seconds before tokens actually expiry. Th…
Browse files Browse the repository at this point in the history
…is creates a necessary buffer for expiring tokens and prevents an bad UX where users are lose their current work.
  • Loading branch information
wparad committed Sep 17, 2023
1 parent 95a81c0 commit 6f6f679
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 8 deletions.
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ class LoginClient {
try {
const tokenResult = await this.httpClient.post(`/authentication/${authRequest.nonce}/tokens`, this.enableCredentials, request);
const idToken = jwtManager.decode(tokenResult.data.id_token);
const expiry = tokenResult.data.expires_in && new Date(Date.now() + tokenResult.data.expires_in * 1000) || new Date(idToken.exp * 1000);
const expiry = idToken.exp && new Date(idToken.exp * 1000) || tokenResult.data.expires_in && new Date(Date.now() + tokenResult.data.expires_in * 1000);
document.cookie = cookieManager.serialize('authorization', tokenResult.data.access_token || '', { expires: expiry, path: '/', sameSite: 'strict' });
userIdentityTokenStorageManager.set(tokenResult.data.id_token, expiry);
userSessionResolver();
Expand Down Expand Up @@ -227,7 +227,7 @@ class LoginClient {
// * This prevents canonical replay attacks, and fall through. If the user is already logged in, then the new log in attempt is ignored.
if (!authRequest.nonce || authRequest.nonce === urlSearchParams.get('nonce')) {
const idToken = jwtManager.decode(urlSearchParams.get('id_token'));
const expiry = Number(urlSearchParams.get('expires_in')) && new Date(Date.now() + Number(urlSearchParams.get('expires_in')) * 1000) || new Date(idToken.exp * 1000);
const expiry = idToken.exp && new Date(idToken.exp * 1000) || Number(urlSearchParams.get('expires_in')) && new Date(Date.now() + Number(urlSearchParams.get('expires_in')) * 1000);
document.cookie = cookieManager.serialize('authorization', urlSearchParams.get('access_token') || '', { expires: expiry, path: '/', sameSite: 'strict' });
userIdentityTokenStorageManager.set(urlSearchParams.get('id_token'), expiry);
userSessionResolver();
Expand All @@ -251,7 +251,7 @@ class LoginClient {
// In the case that the session contains non cookie based data, store it back to the cookie for this domain
if (sessionResult.data.access_token) {
const idToken = jwtManager.decode(sessionResult.data.id_token);
const expiry = sessionResult.data.expires_in && new Date(Date.now() + sessionResult.data.expires_in * 1000) || new Date(idToken.exp * 1000);
const expiry = idToken.exp && new Date(idToken.exp * 1000) || sessionResult.data.expires_in && new Date(Date.now() + sessionResult.data.expires_in * 1000);
document.cookie = cookieManager.serialize('authorization', sessionResult.data.access_token || '', { expires: expiry, path: '/', sameSite: 'strict' });
userIdentityTokenStorageManager.set(sessionResult.data.id_token, expiry);
}
Expand Down
30 changes: 25 additions & 5 deletions src/jwtManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@ const base64url = require('./base64url');

class JwtManager {
decode(token) {
if (!token) {
return null;
}

try {
return token && JSON.parse(base64url.decode(token.split('.')[1]));
const parsedToken = JSON.parse(base64url.decode(token.split('.')[1]));
// If the identity expires in less than 10 seconds from now, assume it is already expired.
// * This blocks issues with intermittent access, and subsequent issues when the token has a limited finite lifetime
// * All the Authress token server returns 5 second long JWT lifetimes to prevent issues with browsers refusing 0 second long lifetimes, so a buffer is required
if (parsedToken.exp) {
parsedToken.exp = parsedToken.exp - 10;
}
return parsedToken;
} catch (error) {
return null;
}
Expand All @@ -26,11 +37,20 @@ class JwtManager {
}

decodeFull(token) {
if (!token) {
return null;
}

try {
return token && {
header: JSON.parse(base64url.decode(token.split('.')[0])),
payload: JSON.parse(base64url.decode(token.split('.')[1]))
};
const header = JSON.parse(base64url.decode(token.split('.')[0]));
const payload = JSON.parse(base64url.decode(token.split('.')[1]));
// If the identity expires in less than 10 seconds from now, assume it is already expired.
// * This blocks issues with intermittent access, and subsequent issues when the token has a limited finite lifetime
// * All the Authress token server returns 5 second long JWT lifetimes to prevent issues with browsers refusing 0 second long lifetimes, so a buffer is required
if (payload.exp) {
payload.exp = payload.exp - 10;
}
return { header, payload };
} catch (error) {
return null;
}
Expand Down
1 change: 1 addition & 0 deletions src/userIdentityTokenStorageManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class UserIdentityTokenStorageManager {
if (!idToken) {
return this.getUserCookie();
}

if (expiry < Date.now()) {
return null;
}
Expand Down

0 comments on commit 6f6f679

Please sign in to comment.