@@ -32,6 +32,67 @@ const token_endpoint_auth_method = OAUTH2_CLIENT_SECRET
32
32
? 'client_secret_basic'
33
33
: 'none' ;
34
34
35
+ const calculateExpirationDate = ( expiresIn ?: number ) => {
36
+ if ( ! expiresIn ) {
37
+ return undefined ;
38
+ }
39
+
40
+ return Math . floor ( Date . now ( ) / 1000 ) + expiresIn ;
41
+ } ;
42
+ const getWellKnownData = async ( ) => {
43
+ const wellKnownUrl = new URL ( OIDC_CONF_FULL_WELL_KNOWN_URL ) ;
44
+ const wellKnown = await fetch ( wellKnownUrl ) ;
45
+ return await wellKnown . json ( ) ;
46
+ } ;
47
+ async function refreshAccessToken ( token : JWT ) : Promise < JWT > {
48
+ const { token_endpoint } = await getWellKnownData ( ) ;
49
+
50
+ try {
51
+ const raw = {
52
+ client_id : OAUTH2_CLIENT_ID ,
53
+ client_secret : OAUTH2_CLIENT_SECRET ,
54
+ grant_type : 'refresh_token' ,
55
+ refresh_token : token . refreshToken as string ,
56
+ } ;
57
+
58
+ const response = await fetch ( token_endpoint , {
59
+ method : 'POST' ,
60
+ body : new URLSearchParams ( raw ) ,
61
+ } ) ;
62
+
63
+ if ( response . ok ) {
64
+ const data : {
65
+ access_token : string ;
66
+ expires_in : number ;
67
+ refresh_token : string ;
68
+ refresh_expires_in : number ;
69
+ } = await response . json ( ) ;
70
+
71
+ return {
72
+ ...token ,
73
+ accessToken : data . access_token ,
74
+ accessTokenExpiresAt : calculateExpirationDate ( data . expires_in ) ,
75
+ refreshToken : data . refresh_token ,
76
+ refreshTokenExpiresAt : calculateExpirationDate (
77
+ data . refresh_expires_in ,
78
+ ) ,
79
+ } ;
80
+ } else {
81
+ console . error (
82
+ 'An error occurred while refreshing the access token: ' ,
83
+ response . statusText ,
84
+ ) ;
85
+ return token ;
86
+ }
87
+ } catch ( error ) {
88
+ console . error (
89
+ 'An error occurred while refreshing the access token: ' ,
90
+ error ,
91
+ ) ;
92
+ return token ;
93
+ }
94
+ }
95
+
35
96
const wfoProvider : OAuthConfig < WfoUserProfile > = {
36
97
id : NEXTAUTH_PROVIDER_ID ,
37
98
name : NEXTAUTH_PROVIDER_NAME ,
@@ -74,21 +135,46 @@ export const authOptions: AuthOptions = {
74
135
providers : isOauth2Enabled ? [ wfoProvider ] : [ ] ,
75
136
callbacks : {
76
137
async jwt ( { token, account, profile } ) {
138
+ // First time after signing in
77
139
// The "account" is only available right after signing in -- adding useful data to the token
78
140
if ( account ) {
79
- token . accessToken = account . access_token ;
80
- token . profile = profile ;
141
+ return {
142
+ ...token ,
143
+ accessToken : account . access_token ,
144
+ refreshToken : account . refresh_token ,
145
+ accessTokenExpiresAt : account . expires_at as number ,
146
+ refreshTokenExpiresAt : calculateExpirationDate (
147
+ account . refresh_expires_in as number ,
148
+ ) ,
149
+ profile,
150
+ } ;
81
151
}
82
- return token ;
152
+
153
+ const now = Math . floor ( Date . now ( ) / 1000 ) ;
154
+ if (
155
+ typeof token . accessTokenExpiresAt === 'number' &&
156
+ now < token . accessTokenExpiresAt
157
+ ) {
158
+ return token ;
159
+ }
160
+
161
+ return await refreshAccessToken ( token ) ;
83
162
} ,
84
- async session ( { session, token } : { session : WfoSession ; token : JWT } ) {
163
+ async session ( {
164
+ session,
165
+ token,
166
+ } : {
167
+ session : WfoSession ;
168
+ token : JWT ;
169
+ } ) : Promise < WfoSession > {
85
170
// Assign data to the session to be available in the client through the useSession hook
86
- session . profile = token . profile as WfoUserProfile | undefined ;
87
- session . accessToken = token . accessToken
88
- ? String ( token . accessToken )
89
- : '' ;
90
-
91
- return session ;
171
+ return {
172
+ ...session ,
173
+ profile : token . profile as WfoUserProfile | undefined ,
174
+ accessToken : token . accessToken ? String ( token . accessToken ) : '' ,
175
+ accessTokenExpiresAt : token . accessTokenExpiresAt as number ,
176
+ refreshTokenExpiresAt : token . refreshTokenExpiresAt as number ,
177
+ } ;
92
178
} ,
93
179
} ,
94
180
} ;
0 commit comments