Skip to content

Commit

Permalink
fix(client): use timestamp before issue token request to ensure expir…
Browse files Browse the repository at this point in the history
…esAt is smaller than token exp (#522)
  • Loading branch information
charIeszhao committed Jul 14, 2023
1 parent 58c66c6 commit 5ed5b92
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-apples-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@logto/client": patch
---

Add backward time shift to local storage cached `expiresAt`, to ensure it is always smaller than the actual `exp` timestamp in access token claims
70 changes: 36 additions & 34 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
type CodeTokenResponse,
type IdTokenClaims,
type UserInfoResponse,
type InteractionMode,
Expand Down Expand Up @@ -170,8 +169,7 @@ export default class LogtoClient {
}

async handleSignInCallback(callbackUri: string) {
const { logtoConfig, adapter } = this;
const { requester } = adapter;
const { requester } = this.adapter;
const signInSession = await this.getSignInSession();

if (!signInSession) {
Expand All @@ -181,21 +179,37 @@ export default class LogtoClient {
const { redirectUri, state, codeVerifier } = signInSession;
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);

const { appId: clientId } = logtoConfig;
// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
const accessTokenKey = buildAccessTokenKey();
const { appId: clientId } = this.logtoConfig;
const { tokenEndpoint } = await this.getOidcConfig();
const codeTokenResponse = await fetchTokenByAuthorizationCode(
{
clientId,
tokenEndpoint,
redirectUri,
codeVerifier,
code,
},
requester
);
const requestedAt = Math.round(Date.now() / 1000);
const { idToken, refreshToken, accessToken, scope, expiresIn } =
await fetchTokenByAuthorizationCode(
{
clientId,
tokenEndpoint,
redirectUri,
codeVerifier,
code,
},
requester
);

await this.verifyIdToken(idToken);
await this.setRefreshToken(refreshToken ?? null);
await this.setIdToken(idToken);

await this.verifyIdToken(codeTokenResponse.idToken);
await this.saveCodeToken(codeTokenResponse);
this.accessTokenMap.set(accessTokenKey, {
token: accessToken,
scope,
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
* in the token claims. It is utilized by the client to determine if the cached access token
* has expired and when a new access token should be requested.
*/
expiresAt: requestedAt + expiresIn,
});
await this.saveAccessTokenMap();
await this.setSignInSession(null);
}

Expand Down Expand Up @@ -264,6 +278,7 @@ export default class LogtoClient {
const accessTokenKey = buildAccessTokenKey(resource);
const { appId: clientId } = this.logtoConfig;
const { tokenEndpoint } = await this.getOidcConfig();
const requestedAt = Math.round(Date.now() / 1000);
const { accessToken, refreshToken, idToken, scope, expiresIn } = await fetchTokenByRefreshToken(
{
clientId,
Expand All @@ -277,7 +292,11 @@ export default class LogtoClient {
this.accessTokenMap.set(accessTokenKey, {
token: accessToken,
scope,
expiresAt: Math.round(Date.now() / 1000) + expiresIn,
/** The `expiresAt` variable provides an approximate estimation of the actual `exp` property
* in the token claims. It is utilized by the client to determine if the cached access token
* has expired and when a new access token should be requested.
*/
expiresAt: requestedAt + expiresIn,
});

await this.saveAccessTokenMap();
Expand All @@ -299,23 +318,6 @@ export default class LogtoClient {
await verifyIdToken(idToken, appId, issuer, jwtVerifyGetKey);
}

private async saveCodeToken({
refreshToken,
idToken,
scope,
accessToken,
expiresIn,
}: CodeTokenResponse) {
await this.setRefreshToken(refreshToken ?? null);
await this.setIdToken(idToken);

// NOTE: Will add scope to accessTokenKey when needed. (Linear issue LOG-1589)
const accessTokenKey = buildAccessTokenKey();
const expiresAt = Date.now() / 1000 + expiresIn;
this.accessTokenMap.set(accessTokenKey, { token: accessToken, scope, expiresAt });
await this.saveAccessTokenMap();
}

private async saveAccessTokenMap() {
const data: Record<string, AccessToken> = {};

Expand Down

0 comments on commit 5ed5b92

Please sign in to comment.