Skip to main content

Remix Token Renewal Flow

Overview

The renewal of tokens happens at two key points in our system: within the Remix middleware and during GraphQL server requests. This document details both processes, ensuring that tokens are renewed as needed to maintain user authentication.

Token Renewal at remix

The middleware in Remix checks if a route is protected and whether the user has the necessary rights to access the page. If the user is not authenticated or if their idToken has expired, the middleware attempts to renew the token.

// File Location: https://github.com/CDEBase/adminIde-stack/blob/508f42f4dffa5e40f612c68120b1d875be1b644c/packages-modules/user-auth0/client/src/auth/RemixAuth0Authenticator.server.ts#L82

let authInfo = await this.isAuthenticated(request);
if (!authInfo) {
return { sessionInfo: null, redirect: config.LOGIN_PATH }; // Consider using failureRedirect option
}
const { expiresAt, refreshToken, rememberMe = false } = authInfo as SessionCookiePayload;
if (this.isTokenValid(expiresAt)) {
return { sessionInfo: authInfo };
}
if (this.canRenewToken({ expiresAt, rememberMe })) {
let result;
try {
result = await this.getNewToken(refreshToken);
} catch (err) {
console.error('RenewToken failed!', err);
throw new Error(AuthErrors.ResponseNotSuccessful);
}
if (result && result.data && result.data.renewAuthUsingRefreshToken) {
const renewedIdToken = result.data.renewAuthUsingRefreshToken.idToken;
const renewedAccessToken = result.data.renewAuthUsingRefreshToken.accessToken;
const decodedJwt = jwtDecode(renewedIdToken);
let newExpiryTime = decodedJwt.exp;
// make use of given expiresIn, if not available use default 8 hours
if (config.isDev) {
// this is only for logging purpose until we see both are equal
const expiresIn = result.data.renewAuthUsingRefreshToken.expiresIn;
const currentTime = Math.floor(Date.now() / 1000);
const calculatedExpiresAt = currentTime + expiresIn;

console.log(
'--- calculatedExpiresAt: ',
calculatedExpiresAt,
'--- jwtDecoded.exp: ',
newExpiryTime,
);
// above one giving 24 hours but jwt is not created that long.
console.log('--- setting to jwtDecoded.exp until they both gives equal');
}
const session = await this.sessionStore.getSession(request.headers.get('Cookie'));
const sessionPayload = session.get(SESSION_KEY);
const renewedSessionInfo = {
...sessionPayload,
idToken: renewedIdToken,
expiresAt: newExpiryTime,
accessToken: renewedAccessToken,
};

if (!renewedIdToken) {
return { sessionInfo: null, redirect: config.LOGIN_PATH, expireCookie: true };
}

return { sessionInfo: renewedSessionInfo, renewed: true }; // Ensuring redirect logic is clear
} else {
return { sessionInfo: null, redirect: config.LOGIN_PATH };
}
}

return { sessionInfo: null, redirect: config.LOGIN_PATH, expireCookie: true };

Token Renewal at Graphql endpoint.

The second place where token renewal occurs is within the middleware executed on the GraphQL server. This ensures that tokens are renewed even if the user has been on the same page for longer than the token expiration time, as the Remix middleware won't execute in this case.

// File location: https://github.com/CDEBase/adminIde-stack/blob/508f42f4dffa5e40f612c68120b1d875be1b644c/packages-modules/user-auth0/server/src/auth/auth0/auth0RenewTokenHelper.ts#L35

export const auth0RenewTokenHelper = async (requestResponsePair, logger: CdmLogger.ILogger) => {
try {
const { req, res } = requestResponsePair;
const sessionStorage = await getSessionStorage({
request: req,
context: { redisClient: req.redisClient } as IAppLoadContext,
});
const session = await sessionStorage.getSession(req.headers['cookie']);

const sessionPayload = session.get(SESSION_KEY);
logger.trace('(auth0RenewTokenHelper) session cookie payload: [%j]', sessionPayload);

const idToken = sessionPayload?.idToken;
const expiresAt = sessionPayload?.expiresAt;
const refreshToken = sessionPayload?.refreshToken;
logger.trace(
`(auth0RenewTokenHelper) ID Token: ${
idToken ? '********' : 'None'
}, Expires At: ${expiresAt}, Refresh Token: ${refreshToken ? '********' : 'None'}`,
);

const isRenewApplicable = canRenewToken(session, logger);
logger.trace(`(auth0RenewTokenHelper) Is token renewal applicable? ${isRenewApplicable}`);

if (isRenewApplicable) {
const result = await renewAuthUsingRefreshToken(refreshToken, logger);
const renewedIdToken = result?.idToken;
const renewedAccessToken = result?.accessToken;
logger.trace(
'(auth0RenewTokenHelper) Token renewed using refresh token:',
renewedIdToken ? '********' : 'None',
);

if (renewedIdToken) {
const decodedJwt = jwtDecode(renewedIdToken);
const newExpiryTime = decodedJwt.exp;
logger.trace(`(auth0RenewTokenHelper) New token expiry time: ${newExpiryTime}`);

await session.set(SESSION_KEY, {
...sessionPayload,
idToken: renewedIdToken,
expiresAt: newExpiryTime * 1000,
accessToken: renewedAccessToken,
});

const updatedSession = await sessionStorage.commitSession(session);

if (updatedSession) {
res.setHeader('Set-Cookie', updatedSession);
req.headers['authorization'] = `Bearer ${renewedIdToken}`; // Update authorization header for the current request
logger.trace('(auth0RenewTokenHelper) Token renewal successful, headers updated');
} else {
logger.error('(auth0RenewTokenHelper) Updated session is undefined');
throw new Error('Failed to update session');
}
}
} else {
logger.trace('(auth0RenewTokenHelper) Token is not renewable');
throw new Error('Failed to renew token');
}
} catch (error) {
logger.error(error, '(auth0RenewTokenHelper) AUTH RENEW TOKEN ERROR:');
throw new AuthRenewTokenError();
}
};