33 * This is a demo implementation, please create your own handlers
44 */
55
6- import { sign , verify } from 'jsonwebtoken'
6+ import { jwtVerify , SignJWT } from 'jose'
7+ import type { JWTPayload } from 'jose'
78import { z } from 'zod'
89
910/**
1011 * This is a demo secret.
1112 * Please ensure that your secret is properly protected.
1213 */
13- const SECRET = 'dummy'
14+ const SECRET = new TextEncoder ( ) . encode ( 'dummy' )
1415
1516/** 30 seconds */
1617const ACCESS_TOKEN_TTL = 30
1718
18- export interface User {
19- username : string
20- name : string
21- picture : string
22- }
23-
24- export interface JwtPayload extends User {
25- scope : Array < 'test' | 'user' >
26- exp ?: number
27- }
19+ export const userSchema = z . object ( {
20+ username : z . string ( ) . min ( 1 ) ,
21+ name : z . string ( ) ,
22+ picture : z . string ( ) . optional ( ) ,
23+ scope : z . enum ( [ 'test' , 'user' ] ) . array ( ) . optional ( ) ,
24+ } )
25+ export type User = z . infer < typeof userSchema >
2826
2927interface TokensByUser {
3028 access : Map < string , string >
@@ -68,15 +66,10 @@ interface UserTokens {
6866 * Demo function for signing user tokens.
6967 * Your implementation may differ.
7068 */
71- export function createUserTokens ( user : User ) : Promise < UserTokens > {
72- const tokenData : JwtPayload = { ...user , scope : [ 'test' , 'user' ] }
73- const accessToken = sign ( tokenData , SECRET , {
74- expiresIn : ACCESS_TOKEN_TTL
75- } )
76- const refreshToken = sign ( tokenData , SECRET , {
77- // 1 day
78- expiresIn : 60 * 60 * 24
79- } )
69+ export async function createUserTokens ( user : User ) : Promise < UserTokens > {
70+ const tokenData : JWTPayload = { ...user , scope : [ 'test' , 'user' ] }
71+ const accessToken = await createSignedJwt ( tokenData , ACCESS_TOKEN_TTL )
72+ const refreshToken = await createSignedJwt ( tokenData , /* 1 day */ 60 * 60 * 24 )
8073
8174 // Naive implementation - please implement properly yourself!
8275 const userTokens : TokensByUser = tokensByUser . get ( user . username ) ?? {
@@ -87,18 +80,18 @@ export function createUserTokens(user: User): Promise<UserTokens> {
8780 userTokens . refresh . set ( refreshToken , accessToken )
8881 tokensByUser . set ( user . username , userTokens )
8982
90- // Emulate async work
91- return Promise . resolve ( {
83+ return {
9284 accessToken,
9385 refreshToken
94- } )
86+ }
9587}
9688
9789/**
9890 * Function for getting the data from a JWT
9991 */
100- export function decodeToken ( token : string ) : JwtPayload | undefined {
101- return verify ( token , SECRET ) as JwtPayload | undefined
92+ export async function decodeToken ( token : string ) : Promise < JWTPayload > {
93+ const verified = await jwtVerify ( token , SECRET )
94+ return verified . payload
10295}
10396
10497/**
@@ -138,33 +131,29 @@ export function invalidateAccessToken(tokensByUser: TokensByUser, accessToken: s
138131 tokensByUser . access . delete ( accessToken )
139132}
140133
141- export function refreshUserAccessToken ( tokensByUser : TokensByUser , refreshToken : string ) : Promise < UserTokens | undefined > {
134+ export async function refreshUserAccessToken ( tokensByUser : TokensByUser , refreshToken : string ) : Promise < UserTokens | undefined > {
142135 // Get the access token
143136 const oldAccessToken = tokensByUser . refresh . get ( refreshToken )
144137 if ( ! oldAccessToken ) {
145- // Promises to emulate async work (e.g. of a DB call)
146- return Promise . resolve ( undefined )
138+ return
147139 }
148140
149141 // Invalidate old access token
150142 invalidateAccessToken ( tokensByUser , oldAccessToken )
151143
152144 // Get the user data. In a real implementation this is likely a DB call.
153145 // In this demo we simply re-use the existing JWT data
154- const jwtUser = decodeToken ( refreshToken )
146+ const jwtUser = await decodeToken ( refreshToken )
155147 if ( ! jwtUser ) {
156- return Promise . resolve ( undefined )
148+ return
157149 }
158150
159- const user : User = {
160- username : jwtUser . username ,
161- picture : jwtUser . picture ,
162- name : jwtUser . name
151+ const user = userSchema . safeParse ( jwtUser )
152+ if ( ! user . success ) {
153+ return
163154 }
164155
165- const accessToken = sign ( { ...user , scope : [ 'test' , 'user' ] } , SECRET , {
166- expiresIn : 60 * 5 // 5 minutes
167- } )
156+ const accessToken = await createSignedJwt ( { ...user . data , scope : [ 'test' , 'user' ] } , /* 5 minutes */ 60 * 5 )
168157 tokensByUser . refresh . set ( refreshToken , accessToken )
169158 tokensByUser . access . set ( accessToken , refreshToken )
170159
@@ -179,3 +168,12 @@ export function extractTokenFromAuthorizationHeader(authorizationHeader: string)
179168 ? authorizationHeader . slice ( 7 )
180169 : authorizationHeader
181170}
171+
172+ function createSignedJwt ( payload : JWTPayload , ttlInSeconds : number ) : Promise < string > {
173+ const unixTimestampNow = Math . floor ( Date . now ( ) / 1000 )
174+
175+ return new SignJWT ( payload )
176+ . setProtectedHeader ( { alg : 'HS256' } )
177+ . setExpirationTime ( unixTimestampNow + ttlInSeconds )
178+ . sign ( SECRET )
179+ }
0 commit comments