Skip to content

Commit c9e0cd4

Browse files
1423 Adds refresh token support
1 parent 4a6d528 commit c9e0cd4

File tree

1 file changed

+96
-10
lines changed

1 file changed

+96
-10
lines changed

pages/api/auth/[...nextauth].ts

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,67 @@ const token_endpoint_auth_method = OAUTH2_CLIENT_SECRET
3232
? 'client_secret_basic'
3333
: 'none';
3434

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+
3596
const wfoProvider: OAuthConfig<WfoUserProfile> = {
3697
id: NEXTAUTH_PROVIDER_ID,
3798
name: NEXTAUTH_PROVIDER_NAME,
@@ -74,21 +135,46 @@ export const authOptions: AuthOptions = {
74135
providers: isOauth2Enabled ? [wfoProvider] : [],
75136
callbacks: {
76137
async jwt({ token, account, profile }) {
138+
// First time after signing in
77139
// The "account" is only available right after signing in -- adding useful data to the token
78140
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+
};
81151
}
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);
83162
},
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> {
85170
// 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+
};
92178
},
93179
},
94180
};

0 commit comments

Comments
 (0)