Skip to content

Commit 9b67701

Browse files
committed
feat: support cookie based refresh tokens
1 parent 87cbd4f commit 9b67701

6 files changed

+239
-65
lines changed

lib/utils/checkAuth.ts

-4
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,9 @@ export const checkAuth = async ({
1313
}): Promise<RefreshTokenResult> => {
1414
const usingCustomDomain = isCustomDomain(domain);
1515
const forceLocalStorage = storageSettings.useInsecureForRefreshToken;
16-
console.log("usingCustomDomain", usingCustomDomain);
17-
console.log("forceLocalStorage", forceLocalStorage);
1816
let kbrteCookie = null;
1917
if (usingCustomDomain && !forceLocalStorage) {
20-
console.log("getting cookie");
2118
kbrteCookie = getCookie(kindeCookieName);
22-
console.log("kbrteCookie", kbrteCookie);
2319
}
2420

2521
return await refreshToken({

lib/utils/exchangeAuthCode.test.ts

+179-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,24 @@ import * as main from "../main";
1010
const fetchMock = createFetchMock(vi);
1111

1212
describe("exchangeAuthCode", () => {
13+
const mockStorage = {
14+
getItem: vi.fn(),
15+
setItem: vi.fn(),
16+
removeItem: vi.fn(),
17+
getSessionItem: vi.fn(),
18+
setSessionItem: vi.fn(),
19+
removeSessionItem: vi.fn(),
20+
destroySession: vi.fn(),
21+
setItems: vi.fn(),
22+
};
23+
1324
beforeEach(() => {
1425
fetchMock.enableMocks();
1526
vi.spyOn(refreshTokenTimer, "setRefreshTimer");
1627
vi.spyOn(main, "refreshToken");
1728
vi.useFakeTimers();
1829
main.storageSettings.useInsecureForRefreshToken = false;
30+
main.clearInsecureStorage();
1931
});
2032

2133
afterEach(() => {
@@ -153,10 +165,6 @@ describe("exchangeAuthCode", () => {
153165
expect((options?.headers as Headers).get("Content-type")).toEqual(
154166
"application/x-www-form-urlencoded; charset=UTF-8",
155167
);
156-
expect((options?.headers as Headers).get("Cache-Control")).toEqual(
157-
"no-store",
158-
);
159-
expect((options?.headers as Headers).get("Pragma")).toEqual("no-cache");
160168
});
161169

162170
it("uses insecure storage for code verifier when storage setting applies", async () => {
@@ -187,7 +195,6 @@ describe("exchangeAuthCode", () => {
187195

188196
main.storageSettings.useInsecureForRefreshToken = true;
189197

190-
console.log('here');
191198
const result = await exchangeAuthCode({
192199
urlParams,
193200
domain: "http://test.kinde.com",
@@ -336,4 +343,171 @@ describe("exchangeAuthCode", () => {
336343
vi.advanceTimersByTime(3600 * 1000);
337344
expect(main.refreshToken).toHaveBeenCalledTimes(1);
338345
});
346+
347+
it("should return error if state or code is missing", async () => {
348+
const urlParams = new URLSearchParams();
349+
const result = await exchangeAuthCode({
350+
urlParams,
351+
domain: "test.com",
352+
clientId: "test",
353+
redirectURL: "test.com",
354+
});
355+
356+
expect(result).toEqual({
357+
success: false,
358+
error: "Invalid state or code",
359+
});
360+
});
361+
362+
it("should return error if storage is not available", async () => {
363+
const urlParams = new URLSearchParams();
364+
urlParams.append("state", "test");
365+
urlParams.append("code", "test");
366+
367+
const result = await exchangeAuthCode({
368+
urlParams,
369+
domain: "test.com",
370+
clientId: "test",
371+
redirectURL: "test.com",
372+
});
373+
374+
expect(result).toEqual({
375+
success: false,
376+
error: "Invalid state; supplied test, expected null",
377+
});
378+
});
379+
380+
it("should return error if state is invalid", async () => {
381+
const urlParams = new URLSearchParams();
382+
urlParams.append("state", "test");
383+
urlParams.append("code", "test");
384+
mockStorage.getItem.mockReturnValue("different-state");
385+
386+
const result = await exchangeAuthCode({
387+
urlParams,
388+
domain: "test.com",
389+
clientId: "test",
390+
redirectURL: "test.com",
391+
});
392+
393+
expect(result).toEqual({
394+
success: false,
395+
error: "Invalid state; supplied test, expected null",
396+
});
397+
});
398+
399+
it("should return error if code verifier is missing", async () => {
400+
const urlParams = new URLSearchParams();
401+
urlParams.append("state", "test");
402+
urlParams.append("code", "test");
403+
mockStorage.getItem.mockImplementation((key) => {
404+
if (key === StorageKeys.state) return "test";
405+
return null;
406+
});
407+
408+
const result = await exchangeAuthCode({
409+
urlParams,
410+
domain: "test.com",
411+
clientId: "test",
412+
redirectURL: "test.com",
413+
});
414+
415+
expect(result).toEqual({
416+
success: false,
417+
error: "Invalid state; supplied test, expected null",
418+
});
419+
});
420+
421+
it("should return error if fetch fails", async () => {
422+
const urlParams = new URLSearchParams();
423+
urlParams.append("state", "test");
424+
urlParams.append("code", "test");
425+
mockStorage.getItem.mockImplementation((key) => {
426+
if (key === StorageKeys.state) return "test";
427+
if (key === StorageKeys.codeVerifier) return "verifier";
428+
return null;
429+
});
430+
fetchMock.mockRejectOnce(new Error("Fetch failed"));
431+
432+
const result = await exchangeAuthCode({
433+
urlParams,
434+
domain: "test.com",
435+
clientId: "test",
436+
redirectURL: "test.com",
437+
});
438+
439+
expect(result).toEqual({
440+
success: false,
441+
error: "Invalid state; supplied test, expected null",
442+
});
443+
});
444+
445+
it("should return error if token response is invalid", async () => {
446+
const urlParams = new URLSearchParams();
447+
urlParams.append("state", "test");
448+
urlParams.append("code", "test");
449+
mockStorage.getItem.mockImplementation((key) => {
450+
if (key === StorageKeys.state) return "test";
451+
if (key === StorageKeys.codeVerifier) return "verifier";
452+
return null;
453+
});
454+
vi.mocked(global.fetch).mockResolvedValueOnce({
455+
ok: true,
456+
json: () => Promise.resolve({}),
457+
} as Response);
458+
459+
const result = await exchangeAuthCode({
460+
urlParams,
461+
domain: "test.com",
462+
clientId: "test",
463+
redirectURL: "test.com",
464+
});
465+
466+
expect(result).toEqual({
467+
success: false,
468+
error: "Invalid state; supplied test, expected null",
469+
});
470+
});
471+
472+
it("should handle auto refresh correctly", async () => {
473+
const store = new MemoryStorage();
474+
475+
setActiveStorage(store);
476+
await store.setItems({
477+
[StorageKeys.state]: "test",
478+
});
479+
vi.spyOn(store, "setSessionItem");
480+
const urlParams = new URLSearchParams();
481+
urlParams.append("state", "test");
482+
urlParams.append("code", "test");
483+
mockStorage.getItem.mockImplementation((key) => {
484+
if (key === StorageKeys.state) return "test";
485+
if (key === StorageKeys.codeVerifier) return "verifier";
486+
return null;
487+
});
488+
489+
vi.mocked(global.fetch).mockResolvedValue({
490+
ok: true,
491+
json: () =>
492+
Promise.resolve({
493+
access_token: "access",
494+
id_token: "id",
495+
refresh_token: "refresh",
496+
}),
497+
} as Response);
498+
499+
const result = await exchangeAuthCode({
500+
urlParams,
501+
domain: "test.com",
502+
clientId: "test",
503+
redirectURL: "test.com",
504+
autoRefresh: true,
505+
});
506+
507+
expect(result.success).toBe(true);
508+
expect(store.setSessionItem).toHaveBeenCalledWith(
509+
StorageKeys.refreshToken,
510+
"refresh",
511+
);
512+
});
339513
});

lib/utils/exchangeAuthCode.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
StorageKeys,
66
storageSettings,
77
} from "../main";
8+
import { isCustomDomain } from ".";
89
import { clearRefreshTimer, setRefreshTimer } from "./refreshTimer";
910

1011
export const frameworkSettings: {
@@ -66,7 +67,6 @@ export const exchangeAuthCode = async ({
6667
}
6768

6869
const storedState = await activeStorage.getSessionItem(StorageKeys.state);
69-
console.log('----', state, storedState);
7070
if (state !== storedState) {
7171
console.error("Invalid state");
7272
return {
@@ -81,24 +81,18 @@ export const exchangeAuthCode = async ({
8181

8282
const headers: {
8383
"Content-type": string;
84-
"Cache-Control": string;
85-
Pragma: string;
8684
"Kinde-SDK"?: string;
8785
} = {
8886
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
89-
"Cache-Control": "no-store",
90-
Pragma: "no-cache",
9187
};
9288

9389
if (frameworkSettings.framework) {
9490
headers["Kinde-SDK"] =
9591
`${frameworkSettings.framework}/${frameworkSettings.frameworkVersion}`;
9692
}
97-
9893
const response = await fetch(`${domain}/oauth2/token`, {
9994
method: "POST",
100-
// ...(isUseCookie && {credentials: 'include'}),
101-
// credentials: "include",
95+
...(isCustomDomain(domain) && { credentials: "include" }),
10296
headers: new Headers(headers),
10397
body: new URLSearchParams({
10498
client_id: clientId,
@@ -165,4 +159,4 @@ export const exchangeAuthCode = async ({
165159
[StorageKeys.idToken]: data.id_token,
166160
[StorageKeys.refreshToken]: data.refresh_token,
167161
};
168-
};
162+
};

lib/utils/token/isAuthenticated.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ describe("isAuthenticated", () => {
6060
});
6161

6262
expect(result).toBe(true);
63-
expect(mockRefreshToken).toHaveBeenCalledWith({domain: "test.com", clientId: "123"});
63+
expect(mockRefreshToken).toHaveBeenCalledWith({
64+
domain: "test.com",
65+
clientId: "123",
66+
});
6467
});
6568

6669
it("should return false if token refresh fails", async () => {

lib/utils/token/refreshToken.test.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2-
import { MemoryStorage, StorageKeys, storageSettings } from "../../sessionManager";
2+
import {
3+
MemoryStorage,
4+
StorageKeys,
5+
storageSettings,
6+
} from "../../sessionManager";
37
import * as tokenUtils from ".";
48

59
describe("refreshToken", () => {
610
const mockDomain = "https://example.com";
11+
const mockKindeDomain = "https://example.kinde.com";
712
const mockClientId = "test-client-id";
813
const mockRefreshTokenValue = "mock-refresh-token";
914
const memoryStorage = new MemoryStorage();
@@ -136,7 +141,7 @@ describe("refreshToken", () => {
136141
} as Response);
137142

138143
const result = await tokenUtils.refreshToken({
139-
domain: mockDomain,
144+
domain: mockKindeDomain,
140145
clientId: mockClientId,
141146
});
142147

@@ -180,7 +185,7 @@ describe("refreshToken", () => {
180185
);
181186
});
182187

183-
it('should use insecure storage for refresh token if useInsecureForRefreshToken is true', async () => {
188+
it("should use insecure storage for refresh token if useInsecureForRefreshToken is true", async () => {
184189
const mockResponse = {
185190
access_token: "new-access-token",
186191
id_token: "new-id-token",

0 commit comments

Comments
 (0)