@@ -268,6 +268,18 @@ export function isInReactExperiment() {
268268 }
269269}
270270
271+ /** Decoded JWT payload state */
272+ export type JwtPayload = {
273+ aud : string ;
274+ exp : number ;
275+ iat : number ;
276+ iss : string ;
277+ jti : string ;
278+ stid : string ;
279+ sub : string ;
280+ scope : Array < string > ;
281+ } ;
282+
271283/**
272284 * External Container for holding JWTs.
273285 *
@@ -335,7 +347,63 @@ export class JwtTokenCache {
335347 */
336348 static hasToken ( sessionToken : string , scope : MfaScope ) {
337349 const key = JwtTokenCache . getKey ( sessionToken , scope ) ;
338- return this . state [ key ] != null ;
350+ const jwt = this . state [ key ] ;
351+ return jwt != null && ! this . isExpired ( jwt ) ;
352+ }
353+
354+ /**
355+ * Checks if a token is expired. If the token cannot be decoded or the exp
356+ * claim cannot be found in the payload, then we will also return false.
357+ * @param token A valid JWT
358+ * @returns True if the token's exp claim is greater than now.
359+ */
360+ static isExpired ( token : string ) {
361+ const decodedJwt = this . decodeTokenPayload ( token ) ;
362+
363+ // Under real use this shouldn't happen. If a token could be decode
364+ // we can't tell if it expired so return false. We get a little fast
365+ // and loose with some our mocks, and this helps keep them simple...
366+ if ( ! decodedJwt ) {
367+ return false ;
368+ }
369+
370+ return decodedJwt . exp * 1000 < Date . now ( ) ;
371+ }
372+
373+ /**
374+ * Decodes a jwt's payload
375+ * @param token
376+ * @returns
377+ */
378+ static decodeTokenPayload ( token : string ) {
379+ try {
380+ const [ , payload ] = token . split ( '.' ) ;
381+ const base64 = payload . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ;
382+ const jsonPayload = decodeURIComponent (
383+ atob ( base64 )
384+ . split ( '' )
385+ . map ( ( c ) => '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) )
386+ . join ( '' )
387+ ) ;
388+ const decoded = JSON . parse ( jsonPayload ) ;
389+
390+ // Type guard for JwtPayload
391+ if (
392+ decoded &&
393+ typeof decoded . aud === 'string' &&
394+ typeof decoded . exp === 'number' &&
395+ typeof decoded . iat === 'number' &&
396+ typeof decoded . iss === 'string' &&
397+ typeof decoded . jti === 'string' &&
398+ typeof decoded . stid === 'string' &&
399+ typeof decoded . sub === 'string' &&
400+ decoded . scope instanceof Array
401+ ) {
402+ return decoded as JwtPayload ;
403+ }
404+ } catch { }
405+
406+ return null ;
339407 }
340408
341409 /**
0 commit comments