Skip to content

Commit c2c875f

Browse files
committed
feat: auto refresh token after code exchange
1 parent ce4f2ce commit c2c875f

File tree

5 files changed

+61
-18
lines changed

5 files changed

+61
-18
lines changed

lib/utils/base64UrlEncode.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@ export const base64UrlEncode = (str: string | ArrayBuffer): string => {
77
if (str instanceof ArrayBuffer) {
88
const numberArray = Array.from<number>(new Uint8Array(str));
99
return btoa(String.fromCharCode.apply(null, numberArray))
10-
.replace(/\+/g, '-')
11-
.replace(/\//g, '_')
12-
.replace(/=+$/, '');
10+
.replace(/\+/g, "-")
11+
.replace(/\//g, "_")
12+
.replace(/=+$/, "");
1313
}
14-
14+
1515
const encoder = new TextEncoder();
1616
const uintArray = encoder.encode(str);
1717
const charArray = Array.from(uintArray);
1818
return btoa(String.fromCharCode.apply(null, charArray))
1919
.replace(/\+/g, "-")
2020
.replace(/\//g, "_")
2121
.replace(/=+$/, "");
22-
2322
};

lib/utils/exchangeAuthCode.ts

+27-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { getActiveStorage, getInsecureStorage, StorageKeys } from "../main";
1+
import {
2+
getActiveStorage,
3+
getInsecureStorage,
4+
refreshToken,
5+
StorageKeys,
6+
} from "../main";
7+
import { clearRefreshTimer, setRefreshTimer } from "./refreshTimer";
28

39
export const frameworkSettings: {
410
framework: string;
@@ -13,6 +19,7 @@ interface ExchangeAuthCodeParams {
1319
domain: string;
1420
clientId: string;
1521
redirectURL: string;
22+
autoReferesh?: boolean;
1623
}
1724

1825
interface ExchangeAuthCodeResult {
@@ -28,6 +35,7 @@ export const exchangeAuthCode = async ({
2835
domain,
2936
clientId,
3037
redirectURL,
38+
autoReferesh = false,
3139
}: ExchangeAuthCodeParams): Promise<ExchangeAuthCodeResult> => {
3240
const state = urlParams.get("state");
3341
const code = urlParams.get("code");
@@ -69,15 +77,16 @@ export const exchangeAuthCode = async ({
6977
StorageKeys.codeVerifier,
7078
)) as string;
7179

80+
7281
const headers: {
7382
"Content-type": string;
74-
// "Cache-Control": string;
75-
// Pragma: string;
83+
"Cache-Control": string;
84+
Pragma: string;
7685
"Kinde-SDK"?: string;
7786
} = {
7887
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
79-
// "Cache-Control": "no-store",
80-
// Pragma: "no-cache",
88+
"Cache-Control": "no-store",
89+
Pragma: "no-cache",
8190
};
8291

8392
if (frameworkSettings.framework) {
@@ -106,11 +115,13 @@ export const exchangeAuthCode = async ({
106115
error: `Token exchange failed: ${response.status} - ${errorText}`,
107116
};
108117
}
118+
clearRefreshTimer()
109119

110120
const data: {
111121
access_token: string;
112122
id_token: string;
113123
refresh_token: string;
124+
expires_in: number;
114125
} = await response.json();
115126

116127
const secureStore = getActiveStorage();
@@ -120,7 +131,17 @@ export const exchangeAuthCode = async ({
120131
[StorageKeys.refreshToken]: data.refresh_token,
121132
});
122133

123-
await activeStorage.removeItems(StorageKeys.state, StorageKeys.nonce, StorageKeys.codeVerifier);
134+
if (autoReferesh) {
135+
setRefreshTimer(data.expires_in * 1000, async () => {
136+
refreshToken(domain, clientId);
137+
});
138+
}
139+
140+
await activeStorage.removeItems(
141+
StorageKeys.state,
142+
StorageKeys.nonce,
143+
StorageKeys.codeVerifier,
144+
);
124145

125146
// Clear all url params
126147
const cleanUrl = (url: URL): URL => {

lib/utils/generateAuthUrl.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ export const generateAuthUrl = async (
1313
domain: string,
1414
type: IssuerRouteTypes = IssuerRouteTypes.login,
1515
options: LoginOptions,
16-
): Promise<{ url: URL; state: string; nonce: string; codeChallenge: string }> => {
16+
): Promise<{
17+
url: URL;
18+
state: string;
19+
nonce: string;
20+
codeChallenge: string;
21+
}> => {
1722
const authUrl = new URL(`${domain}/oauth2/auth`);
1823
const activeStorage = getInsecureStorage();
1924
const searchParams: Record<string, string> = {

lib/utils/refreshTimer.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export let refreshTimer: number | undefined;
2+
3+
export function setRefreshTimer(timer: number, callback: () => void) {
4+
window.setTimeout(callback, timer);
5+
}
6+
7+
export function clearRefreshTimer() {
8+
if (refreshTimer !== undefined) {
9+
window.clearTimeout(refreshTimer);
10+
refreshTimer = undefined;
11+
}
12+
}

lib/utils/token/refreshToken.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getActiveStorage } from ".";
22
import { StorageKeys } from "../../sessionManager";
33
import { sanitizeUrl } from "..";
4+
import { clearRefreshTimer, setRefreshTimer } from "../refreshTimer";
45

56
/**
67
* refreshes the token
@@ -28,23 +29,25 @@ export const refreshToken = async (
2829
return false;
2930
}
3031

31-
const refreshTokenValue = await storage.getSessionItem(
32+
const refreshTokenValue = (await storage.getSessionItem(
3233
StorageKeys.refreshToken,
33-
) as string;
34-
34+
)) as string;
3535

3636
if (!refreshTokenValue) {
3737
console.error("No refresh token found");
3838
return false;
3939
}
4040

41+
clearRefreshTimer();
42+
4143
const response = await fetch(`${sanitizeUrl(domain)}/oauth2/token`, {
4244
method: "POST",
4345
headers: {
44-
"Content-Type": "multipart/form-data",
45-
"Kinde-SDK": "js-utils",
46+
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
47+
"Cache-Control": "no-store",
48+
Pragma: "no-cache",
4649
},
47-
body: JSON.stringify({
50+
body: new URLSearchParams({
4851
refresh_token: refreshTokenValue,
4952
grant_type: "refresh_token",
5053
client_id: clientId,
@@ -59,6 +62,9 @@ export const refreshToken = async (
5962
const data = await response.json();
6063

6164
if (data.access_token) {
65+
setRefreshTimer(data.expires_in * 1000, async () => {
66+
refreshToken(domain, clientId);
67+
});
6268
await storage.setSessionItem(StorageKeys.accessToken, data.access_token);
6369
if (data.id_token) {
6470
await storage.setSessionItem(StorageKeys.idToken, data.id_token);

0 commit comments

Comments
 (0)