Skip to content

Commit bd4c8d0

Browse files
committed
update access token expiry logic
1 parent eadfb06 commit bd4c8d0

2 files changed

Lines changed: 91 additions & 16 deletions

File tree

packages/passport/sdk/src/utils/token.test.ts

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ import { isIdTokenExpired, isAccessTokenExpiredOrExpiring } from './token';
77
const now = Math.floor(Date.now() / 1000);
88
const oneHourLater = now + 3600;
99
const oneHourBefore = now - 3600;
10+
const fifteenSecondsLater = now + 15;
11+
const fortyFiveSecondsLater = now + 45;
1012

1113
const mockExpiredIdToken = encode({
1214
iat: oneHourBefore,
1315
exp: oneHourBefore,
1416
}, 'secret');
17+
1518
export const mockValidIdToken = encode({
1619
iat: now,
1720
exp: oneHourLater,
1821
}, 'secret');
1922

23+
const mockFreshAccessToken = encode({
24+
exp: fortyFiveSecondsLater, // Expires in 45 seconds (outside 30-second buffer)
25+
}, 'secret');
26+
2027
describe('isIdTokenExpired', () => {
2128
it('should return false if idToken is undefined', () => {
2229
expect(isIdTokenExpired(undefined)).toBe(false);
@@ -32,26 +39,83 @@ describe('isIdTokenExpired', () => {
3239
});
3340

3441
describe('isAccessTokenExpiredOrExpiring', () => {
35-
it('should return true if expired is true', () => {
42+
it('should return true if access token is missing', () => {
3643
const user = {
3744
id_token: mockValidIdToken,
38-
expired: true,
45+
access_token: undefined,
46+
} as unknown as OidcUser;
47+
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
48+
});
49+
50+
it('should return true if id token is missing', () => {
51+
const mockValidAccessToken = encode({
52+
exp: oneHourLater,
53+
}, 'secret');
54+
55+
const user = {
56+
id_token: undefined,
57+
access_token: mockValidAccessToken,
3958
} as unknown as OidcUser;
4059
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
4160
});
4261

43-
it('should return false if idToken is valid', () => {
62+
it('should return true if access token is expired', () => {
63+
const mockExpiredAccessToken = encode({
64+
exp: oneHourBefore,
65+
}, 'secret');
66+
4467
const user = {
4568
id_token: mockValidIdToken,
46-
expired: false,
69+
access_token: mockExpiredAccessToken,
4770
} as unknown as OidcUser;
48-
expect(isAccessTokenExpiredOrExpiring(user)).toBe(false);
71+
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
72+
});
73+
74+
it('should return true if access token is expiring within 30 seconds', () => {
75+
const mockExpiringAccessToken = encode({
76+
exp: fifteenSecondsLater, // Expires in 15 seconds (within 30-second buffer)
77+
}, 'secret');
78+
79+
const user = {
80+
id_token: mockValidIdToken,
81+
access_token: mockExpiringAccessToken,
82+
} as unknown as OidcUser;
83+
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
4984
});
5085

51-
it('should return true idToken is expired', () => {
86+
it('should return true if access token is valid but id token is expired', () => {
5287
const user = {
5388
id_token: mockExpiredIdToken,
54-
expired: false,
89+
access_token: mockFreshAccessToken,
90+
} as unknown as OidcUser;
91+
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
92+
});
93+
94+
it('should return false if both tokens are valid and not expiring', () => {
95+
const user = {
96+
id_token: mockValidIdToken,
97+
access_token: mockFreshAccessToken,
98+
} as unknown as OidcUser;
99+
expect(isAccessTokenExpiredOrExpiring(user)).toBe(false);
100+
});
101+
102+
it('should return true if access token is malformed', () => {
103+
const user = {
104+
id_token: mockValidIdToken,
105+
access_token: 'invalid-jwt-token',
106+
} as unknown as OidcUser;
107+
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
108+
});
109+
110+
it('should return true if access token has no exp claim (security vulnerability)', () => {
111+
const accessTokenWithoutExp = encode({
112+
iat: now,
113+
sub: 'user123',
114+
}, 'secret');
115+
116+
const user = {
117+
id_token: mockValidIdToken,
118+
access_token: accessTokenWithoutExp,
55119
} as unknown as OidcUser;
56120
expect(isAccessTokenExpiredOrExpiring(user)).toBe(true);
57121
});

packages/passport/sdk/src/utils/token.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import jwt_decode from 'jwt-decode';
22
import {
33
User as OidcUser,
44
} from 'oidc-client-ts';
5-
import { IdTokenPayload } from '../types';
5+
import { IdTokenPayload, TokenPayload } from '../types';
66

77
export function isIdTokenExpired(idToken: string | undefined): boolean {
88
if (!idToken) {
@@ -15,18 +15,29 @@ export function isIdTokenExpired(idToken: string | undefined): boolean {
1515
}
1616

1717
export function isAccessTokenExpiredOrExpiring(oidcUser: OidcUser): boolean {
18-
const { id_token: idToken, expired, expires_in } = oidcUser;
19-
if (expired) {
20-
return true;
21-
}
18+
const { id_token: idToken, access_token: accessToken } = oidcUser;
2219

23-
// if token will expire in 30 seconds or less, return true
24-
if (expires_in && expires_in <= 30) {
20+
// Handle missing tokens - assume they need to login again
21+
if (!accessToken || !idToken) {
2522
return true;
2623
}
2724

28-
// Handle missing idToken - assume they need to login again
29-
if (!idToken) {
25+
// Decode the access token to check its expiration
26+
try {
27+
const decodedAccessToken = jwt_decode<TokenPayload>(accessToken);
28+
const now = Math.floor(Date.now() / 1000);
29+
30+
// Access tokens without expiration claims are invalid (security vulnerability)
31+
if (!decodedAccessToken.exp) {
32+
return true;
33+
}
34+
35+
// Check if access token is expired or expiring in 30 seconds or less
36+
if (decodedAccessToken.exp <= now + 30) {
37+
return true;
38+
}
39+
} catch (error) {
40+
// If we can't decode the access token, assume it's invalid
3041
return true;
3142
}
3243

0 commit comments

Comments
 (0)