@workos-inc/authkit-nextjs 2.16.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/env-variables.js +3 -2
- package/dist/esm/env-variables.js.map +1 -1
- package/dist/esm/get-authorization-url.js +37 -11
- package/dist/esm/get-authorization-url.js.map +1 -1
- package/dist/esm/types/env-variables.d.ts +3 -2
- package/package.json +2 -2
- package/src/env-variables.ts +4 -2
- package/src/get-authorization-url.spec.ts +134 -18
- package/src/get-authorization-url.ts +53 -15
|
@@ -11,11 +11,12 @@ const WORKOS_COOKIE_DOMAIN = getEnvVariable('WORKOS_COOKIE_DOMAIN');
|
|
|
11
11
|
const WORKOS_COOKIE_MAX_AGE = getEnvVariable('WORKOS_COOKIE_MAX_AGE');
|
|
12
12
|
const WORKOS_COOKIE_NAME = getEnvVariable('WORKOS_COOKIE_NAME');
|
|
13
13
|
const WORKOS_COOKIE_SAMESITE = getEnvVariable('WORKOS_COOKIE_SAMESITE');
|
|
14
|
-
const
|
|
14
|
+
const WORKOS_CLAIM_TOKEN = getEnvVariable('WORKOS_CLAIM_TOKEN');
|
|
15
|
+
const WORKOS_ENABLE_PKCE = getEnvVariable('WORKOS_ENABLE_PKCE');
|
|
15
16
|
// Required env variables
|
|
16
17
|
const WORKOS_API_KEY = (_a = getEnvVariable('WORKOS_API_KEY')) !== null && _a !== void 0 ? _a : '';
|
|
17
18
|
const WORKOS_CLIENT_ID = (_b = getEnvVariable('WORKOS_CLIENT_ID')) !== null && _b !== void 0 ? _b : '';
|
|
18
19
|
const WORKOS_COOKIE_PASSWORD = (_c = getEnvVariable('WORKOS_COOKIE_PASSWORD')) !== null && _c !== void 0 ? _c : '';
|
|
19
20
|
const WORKOS_REDIRECT_URI = (_d = process.env.NEXT_PUBLIC_WORKOS_REDIRECT_URI) !== null && _d !== void 0 ? _d : '';
|
|
20
|
-
export { WORKOS_API_HOSTNAME, WORKOS_API_HTTPS, WORKOS_API_KEY, WORKOS_API_PORT, WORKOS_CLIENT_ID, WORKOS_COOKIE_DOMAIN, WORKOS_COOKIE_MAX_AGE, WORKOS_COOKIE_NAME, WORKOS_COOKIE_PASSWORD, WORKOS_REDIRECT_URI, WORKOS_COOKIE_SAMESITE,
|
|
21
|
+
export { WORKOS_API_HOSTNAME, WORKOS_API_HTTPS, WORKOS_API_KEY, WORKOS_API_PORT, WORKOS_CLAIM_TOKEN, WORKOS_CLIENT_ID, WORKOS_COOKIE_DOMAIN, WORKOS_COOKIE_MAX_AGE, WORKOS_COOKIE_NAME, WORKOS_COOKIE_PASSWORD, WORKOS_REDIRECT_URI, WORKOS_COOKIE_SAMESITE, WORKOS_ENABLE_PKCE, };
|
|
21
22
|
//# sourceMappingURL=env-variables.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-variables.js","sourceRoot":"","sources":["../../src/env-variables.ts"],"names":[],"mappings":"AAAA,0BAA0B;;AAE1B,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,yBAAyB;AACzB,MAAM,mBAAmB,GAAG,cAAc,CAAC,qBAAqB,CAAC,CAAC;AAClE,MAAM,gBAAgB,GAAG,cAAc,CAAC,kBAAkB,CAAC,CAAC;AAC5D,MAAM,eAAe,GAAG,cAAc,CAAC,iBAAiB,CAAC,CAAC;AAC1D,MAAM,oBAAoB,GAAG,cAAc,CAAC,sBAAsB,CAAC,CAAC;AACpE,MAAM,qBAAqB,GAAG,cAAc,CAAC,uBAAuB,CAAC,CAAC;AACtE,MAAM,kBAAkB,GAAG,cAAc,CAAC,oBAAoB,CAAC,CAAC;AAChE,MAAM,sBAAsB,GAAG,cAAc,CAAC,wBAAwB,CAA0C,CAAC;AACjH,MAAM,
|
|
1
|
+
{"version":3,"file":"env-variables.js","sourceRoot":"","sources":["../../src/env-variables.ts"],"names":[],"mappings":"AAAA,0BAA0B;;AAE1B,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,yBAAyB;AACzB,MAAM,mBAAmB,GAAG,cAAc,CAAC,qBAAqB,CAAC,CAAC;AAClE,MAAM,gBAAgB,GAAG,cAAc,CAAC,kBAAkB,CAAC,CAAC;AAC5D,MAAM,eAAe,GAAG,cAAc,CAAC,iBAAiB,CAAC,CAAC;AAC1D,MAAM,oBAAoB,GAAG,cAAc,CAAC,sBAAsB,CAAC,CAAC;AACpE,MAAM,qBAAqB,GAAG,cAAc,CAAC,uBAAuB,CAAC,CAAC;AACtE,MAAM,kBAAkB,GAAG,cAAc,CAAC,oBAAoB,CAAC,CAAC;AAChE,MAAM,sBAAsB,GAAG,cAAc,CAAC,wBAAwB,CAA0C,CAAC;AACjH,MAAM,kBAAkB,GAAG,cAAc,CAAC,oBAAoB,CAAC,CAAC;AAChE,MAAM,kBAAkB,GAAG,cAAc,CAAC,oBAAoB,CAAC,CAAC;AAEhE,yBAAyB;AACzB,MAAM,cAAc,GAAG,MAAA,cAAc,CAAC,gBAAgB,CAAC,mCAAI,EAAE,CAAC;AAC9D,MAAM,gBAAgB,GAAG,MAAA,cAAc,CAAC,kBAAkB,CAAC,mCAAI,EAAE,CAAC;AAClE,MAAM,sBAAsB,GAAG,MAAA,cAAc,CAAC,wBAAwB,CAAC,mCAAI,EAAE,CAAC;AAC9E,MAAM,mBAAmB,GAAG,MAAA,OAAO,CAAC,GAAG,CAAC,+BAA+B,mCAAI,EAAE,CAAC;AAE9E,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,GACnB,CAAC"}
|
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { sealData } from 'iron-session';
|
|
2
2
|
import { headers } from 'next/headers';
|
|
3
|
-
import { WORKOS_CLIENT_ID, WORKOS_COOKIE_PASSWORD,
|
|
3
|
+
import { WORKOS_CLAIM_TOKEN, WORKOS_CLIENT_ID, WORKOS_COOKIE_PASSWORD, WORKOS_ENABLE_PKCE, WORKOS_REDIRECT_URI, } from './env-variables.js';
|
|
4
4
|
import { getWorkOS } from './workos.js';
|
|
5
|
+
async function fetchClaimNonce(baseURL) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(`${baseURL}/x/one-shot-environments/claim-nonces`, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
body: JSON.stringify({
|
|
11
|
+
client_id: WORKOS_CLIENT_ID,
|
|
12
|
+
claim_token: WORKOS_CLAIM_TOKEN,
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
if (response.status !== 409) {
|
|
17
|
+
console.warn(`[authkit-nextjs]: Failed to exchange WORKOS_CLAIM_TOKEN (${response.status}). Try removing WORKOS_CLAIM_TOKEN from your environment variables.`);
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
return data.nonce;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.warn('[authkit-nextjs]: Failed to exchange WORKOS_CLAIM_TOKEN. Try removing WORKOS_CLAIM_TOKEN from your environment variables.', error);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
5
29
|
async function getAuthorizationUrl(options = {}) {
|
|
6
30
|
var _a;
|
|
7
31
|
const { returnPathname, screenHint, organizationId, loginHint, prompt, state: customState } = options;
|
|
@@ -13,6 +37,7 @@ async function getAuthorizationUrl(options = {}) {
|
|
|
13
37
|
const internalState = returnPathname
|
|
14
38
|
? btoa(JSON.stringify({ returnPathname })).replace(/\+/g, '-').replace(/\//g, '_')
|
|
15
39
|
: null;
|
|
40
|
+
const claimNonce = WORKOS_CLAIM_TOKEN ? await fetchClaimNonce(getWorkOS().baseURL) : null;
|
|
16
41
|
const finalState = internalState && customState ? `${internalState}.${customState}` : internalState || customState || undefined;
|
|
17
42
|
const baseOptions = {
|
|
18
43
|
provider: 'authkit',
|
|
@@ -23,18 +48,19 @@ async function getAuthorizationUrl(options = {}) {
|
|
|
23
48
|
organizationId,
|
|
24
49
|
loginHint,
|
|
25
50
|
prompt,
|
|
51
|
+
...(claimNonce && { claimNonce }),
|
|
26
52
|
};
|
|
27
|
-
if (
|
|
28
|
-
|
|
53
|
+
if (WORKOS_ENABLE_PKCE === 'true') {
|
|
54
|
+
const pkce = await getWorkOS().pkce.generate();
|
|
55
|
+
const pkceCookieValue = await sealData({ codeVerifier: pkce.codeVerifier }, { password: WORKOS_COOKIE_PASSWORD, ttl: 600 });
|
|
56
|
+
const url = getWorkOS().userManagement.getAuthorizationUrl({
|
|
57
|
+
...baseOptions,
|
|
58
|
+
codeChallenge: pkce.codeChallenge,
|
|
59
|
+
codeChallengeMethod: pkce.codeChallengeMethod,
|
|
60
|
+
});
|
|
61
|
+
return { url, pkceCookieValue };
|
|
29
62
|
}
|
|
30
|
-
|
|
31
|
-
const pkceCookieValue = await sealData({ codeVerifier: pkce.codeVerifier }, { password: WORKOS_COOKIE_PASSWORD, ttl: 600 });
|
|
32
|
-
const url = getWorkOS().userManagement.getAuthorizationUrl({
|
|
33
|
-
...baseOptions,
|
|
34
|
-
codeChallenge: pkce.codeChallenge,
|
|
35
|
-
codeChallengeMethod: pkce.codeChallengeMethod,
|
|
36
|
-
});
|
|
37
|
-
return { url, pkceCookieValue };
|
|
63
|
+
return { url: getWorkOS().userManagement.getAuthorizationUrl(baseOptions), pkceCookieValue: undefined };
|
|
38
64
|
}
|
|
39
65
|
export { getAuthorizationUrl };
|
|
40
66
|
//# sourceMappingURL=get-authorization-url.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-authorization-url.js","sourceRoot":"","sources":["../../src/get-authorization-url.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,
|
|
1
|
+
{"version":3,"file":"get-authorization-url.js","sourceRoot":"","sources":["../../src/get-authorization-url.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,9 +5,10 @@ declare const WORKOS_COOKIE_DOMAIN: string | undefined;
|
|
|
5
5
|
declare const WORKOS_COOKIE_MAX_AGE: string | undefined;
|
|
6
6
|
declare const WORKOS_COOKIE_NAME: string | undefined;
|
|
7
7
|
declare const WORKOS_COOKIE_SAMESITE: "lax" | "strict" | "none" | undefined;
|
|
8
|
-
declare const
|
|
8
|
+
declare const WORKOS_CLAIM_TOKEN: string | undefined;
|
|
9
|
+
declare const WORKOS_ENABLE_PKCE: string | undefined;
|
|
9
10
|
declare const WORKOS_API_KEY: string;
|
|
10
11
|
declare const WORKOS_CLIENT_ID: string;
|
|
11
12
|
declare const WORKOS_COOKIE_PASSWORD: string;
|
|
12
13
|
declare const WORKOS_REDIRECT_URI: string;
|
|
13
|
-
export { WORKOS_API_HOSTNAME, WORKOS_API_HTTPS, WORKOS_API_KEY, WORKOS_API_PORT, WORKOS_CLIENT_ID, WORKOS_COOKIE_DOMAIN, WORKOS_COOKIE_MAX_AGE, WORKOS_COOKIE_NAME, WORKOS_COOKIE_PASSWORD, WORKOS_REDIRECT_URI, WORKOS_COOKIE_SAMESITE,
|
|
14
|
+
export { WORKOS_API_HOSTNAME, WORKOS_API_HTTPS, WORKOS_API_KEY, WORKOS_API_PORT, WORKOS_CLAIM_TOKEN, WORKOS_CLIENT_ID, WORKOS_COOKIE_DOMAIN, WORKOS_COOKIE_MAX_AGE, WORKOS_COOKIE_NAME, WORKOS_COOKIE_PASSWORD, WORKOS_REDIRECT_URI, WORKOS_COOKIE_SAMESITE, WORKOS_ENABLE_PKCE, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos-inc/authkit-nextjs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.17.0",
|
|
4
4
|
"description": "Authentication and session helpers for using WorkOS & AuthKit with Next.js",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@workos-inc/node": "^8.
|
|
26
|
+
"@workos-inc/node": "^8.9.0",
|
|
27
27
|
"iron-session": "^8.0.4",
|
|
28
28
|
"jose": "^5.10.0",
|
|
29
29
|
"path-to-regexp": "^6.3.0"
|
package/src/env-variables.ts
CHANGED
|
@@ -12,7 +12,8 @@ const WORKOS_COOKIE_DOMAIN = getEnvVariable('WORKOS_COOKIE_DOMAIN');
|
|
|
12
12
|
const WORKOS_COOKIE_MAX_AGE = getEnvVariable('WORKOS_COOKIE_MAX_AGE');
|
|
13
13
|
const WORKOS_COOKIE_NAME = getEnvVariable('WORKOS_COOKIE_NAME');
|
|
14
14
|
const WORKOS_COOKIE_SAMESITE = getEnvVariable('WORKOS_COOKIE_SAMESITE') as 'lax' | 'strict' | 'none' | undefined;
|
|
15
|
-
const
|
|
15
|
+
const WORKOS_CLAIM_TOKEN = getEnvVariable('WORKOS_CLAIM_TOKEN');
|
|
16
|
+
const WORKOS_ENABLE_PKCE = getEnvVariable('WORKOS_ENABLE_PKCE');
|
|
16
17
|
|
|
17
18
|
// Required env variables
|
|
18
19
|
const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY') ?? '';
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
WORKOS_API_HTTPS,
|
|
26
27
|
WORKOS_API_KEY,
|
|
27
28
|
WORKOS_API_PORT,
|
|
29
|
+
WORKOS_CLAIM_TOKEN,
|
|
28
30
|
WORKOS_CLIENT_ID,
|
|
29
31
|
WORKOS_COOKIE_DOMAIN,
|
|
30
32
|
WORKOS_COOKIE_MAX_AGE,
|
|
@@ -32,5 +34,5 @@ export {
|
|
|
32
34
|
WORKOS_COOKIE_PASSWORD,
|
|
33
35
|
WORKOS_REDIRECT_URI,
|
|
34
36
|
WORKOS_COOKIE_SAMESITE,
|
|
35
|
-
|
|
37
|
+
WORKOS_ENABLE_PKCE,
|
|
36
38
|
};
|
|
@@ -5,6 +5,7 @@ import { getWorkOS } from './workos.js';
|
|
|
5
5
|
// Mock dependencies
|
|
6
6
|
const { fakeWorkosInstance } = vi.hoisted(() => ({
|
|
7
7
|
fakeWorkosInstance: {
|
|
8
|
+
baseURL: 'https://api.workos.com',
|
|
8
9
|
userManagement: {
|
|
9
10
|
getAuthorizationUrl: vi.fn(),
|
|
10
11
|
},
|
|
@@ -26,7 +27,7 @@ describe('getAuthorizationUrl', () => {
|
|
|
26
27
|
const workos = getWorkOS();
|
|
27
28
|
beforeEach(() => {
|
|
28
29
|
vi.clearAllMocks();
|
|
29
|
-
delete process.env.
|
|
30
|
+
delete process.env.WORKOS_ENABLE_PKCE;
|
|
30
31
|
fakeWorkosInstance.pkce.generate.mockResolvedValue({
|
|
31
32
|
codeVerifier: 'test-code-verifier',
|
|
32
33
|
codeChallenge: 'test-code-challenge',
|
|
@@ -70,45 +71,122 @@ describe('getAuthorizationUrl', () => {
|
|
|
70
71
|
);
|
|
71
72
|
});
|
|
72
73
|
|
|
73
|
-
describe('
|
|
74
|
-
|
|
74
|
+
describe('claim nonce', () => {
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
delete process.env.WORKOS_CLAIM_TOKEN;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does not fetch nonce when WORKOS_CLAIM_TOKEN is not set', async () => {
|
|
80
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch');
|
|
75
81
|
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
await getAuthorizationUrl({});
|
|
78
84
|
|
|
79
|
-
expect(
|
|
85
|
+
expect(fetchSpy).not.toHaveBeenCalledWith(expect.stringContaining('claim-nonces'), expect.anything());
|
|
80
86
|
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
87
|
+
expect.not.objectContaining({ claimNonce: expect.any(String) }),
|
|
88
|
+
);
|
|
89
|
+
fetchSpy.mockRestore();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('fetches nonce and passes claimNonce when WORKOS_CLAIM_TOKEN is set', async () => {
|
|
93
|
+
process.env.WORKOS_CLAIM_TOKEN = 'test-claim-token';
|
|
94
|
+
const fetchSpy = vi
|
|
95
|
+
.spyOn(globalThis, 'fetch')
|
|
96
|
+
.mockResolvedValueOnce(new Response(JSON.stringify({ nonce: 'test-nonce' }), { status: 201 }));
|
|
97
|
+
|
|
98
|
+
vi.resetModules();
|
|
99
|
+
const { getAuthorizationUrl: freshGetAuthorizationUrl } = await import('./get-authorization-url.js');
|
|
100
|
+
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
101
|
+
|
|
102
|
+
await freshGetAuthorizationUrl({});
|
|
103
|
+
|
|
104
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
105
|
+
'https://api.workos.com/x/one-shot-environments/claim-nonces',
|
|
81
106
|
expect.objectContaining({
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
body: JSON.stringify({ client_id: 'client_1234567890', claim_token: 'test-claim-token' }),
|
|
84
110
|
}),
|
|
85
111
|
);
|
|
86
|
-
expect(
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
113
|
+
expect.objectContaining({ claimNonce: 'test-nonce' }),
|
|
114
|
+
);
|
|
115
|
+
fetchSpy.mockRestore();
|
|
89
116
|
});
|
|
90
117
|
|
|
91
|
-
it('
|
|
118
|
+
it('proceeds without nonce on network error', async () => {
|
|
119
|
+
process.env.WORKOS_CLAIM_TOKEN = 'test-claim-token';
|
|
120
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValueOnce(new Error('Network error'));
|
|
121
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
122
|
+
|
|
123
|
+
vi.resetModules();
|
|
124
|
+
const { getAuthorizationUrl: freshGetAuthorizationUrl } = await import('./get-authorization-url.js');
|
|
92
125
|
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
93
126
|
|
|
94
|
-
|
|
127
|
+
await freshGetAuthorizationUrl({});
|
|
95
128
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
130
|
+
expect.not.objectContaining({ claimNonce: expect.any(String) }),
|
|
131
|
+
);
|
|
132
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
133
|
+
'[authkit-nextjs]: Failed to exchange WORKOS_CLAIM_TOKEN. Try removing WORKOS_CLAIM_TOKEN from your environment variables.',
|
|
134
|
+
expect.any(Error),
|
|
135
|
+
);
|
|
136
|
+
fetchSpy.mockRestore();
|
|
137
|
+
warnSpy.mockRestore();
|
|
99
138
|
});
|
|
100
139
|
|
|
101
|
-
it('
|
|
102
|
-
process.env.
|
|
140
|
+
it('proceeds without nonce on non-OK response', async () => {
|
|
141
|
+
process.env.WORKOS_CLAIM_TOKEN = 'test-claim-token';
|
|
142
|
+
const fetchSpy = vi
|
|
143
|
+
.spyOn(globalThis, 'fetch')
|
|
144
|
+
.mockResolvedValueOnce(new Response('Unauthorized', { status: 401 }));
|
|
145
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
103
146
|
|
|
104
|
-
// Re-import to pick up the new env var
|
|
105
147
|
vi.resetModules();
|
|
106
148
|
const { getAuthorizationUrl: freshGetAuthorizationUrl } = await import('./get-authorization-url.js');
|
|
149
|
+
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
107
150
|
|
|
151
|
+
await freshGetAuthorizationUrl({});
|
|
152
|
+
|
|
153
|
+
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
154
|
+
expect.not.objectContaining({ claimNonce: expect.any(String) }),
|
|
155
|
+
);
|
|
156
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
157
|
+
'[authkit-nextjs]: Failed to exchange WORKOS_CLAIM_TOKEN (401). Try removing WORKOS_CLAIM_TOKEN from your environment variables.',
|
|
158
|
+
);
|
|
159
|
+
fetchSpy.mockRestore();
|
|
160
|
+
warnSpy.mockRestore();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('works with PKCE disabled', async () => {
|
|
164
|
+
process.env.WORKOS_CLAIM_TOKEN = 'test-claim-token';
|
|
165
|
+
process.env.WORKOS_DISABLE_PKCE = 'true';
|
|
166
|
+
const fetchSpy = vi
|
|
167
|
+
.spyOn(globalThis, 'fetch')
|
|
168
|
+
.mockResolvedValueOnce(new Response(JSON.stringify({ nonce: 'test-nonce' }), { status: 201 }));
|
|
169
|
+
|
|
170
|
+
vi.resetModules();
|
|
171
|
+
const { getAuthorizationUrl: freshGetAuthorizationUrl } = await import('./get-authorization-url.js');
|
|
108
172
|
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
109
173
|
|
|
110
174
|
const result = await freshGetAuthorizationUrl({});
|
|
111
175
|
|
|
176
|
+
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
177
|
+
expect.objectContaining({ claimNonce: 'test-nonce' }),
|
|
178
|
+
);
|
|
179
|
+
expect(result.pkceCookieValue).toBeUndefined();
|
|
180
|
+
fetchSpy.mockRestore();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('PKCE', () => {
|
|
185
|
+
it('skips PKCE by default', async () => {
|
|
186
|
+
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
187
|
+
|
|
188
|
+
const result = await getAuthorizationUrl({});
|
|
189
|
+
|
|
112
190
|
expect(fakeWorkosInstance.pkce.generate).not.toHaveBeenCalled();
|
|
113
191
|
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
114
192
|
expect.not.objectContaining({
|
|
@@ -117,5 +195,43 @@ describe('getAuthorizationUrl', () => {
|
|
|
117
195
|
);
|
|
118
196
|
expect(result.pkceCookieValue).toBeUndefined();
|
|
119
197
|
});
|
|
198
|
+
|
|
199
|
+
it('generates PKCE pair when WORKOS_ENABLE_PKCE is set to true', async () => {
|
|
200
|
+
process.env.WORKOS_ENABLE_PKCE = 'true';
|
|
201
|
+
|
|
202
|
+
// Re-import to pick up the new env var
|
|
203
|
+
vi.resetModules();
|
|
204
|
+
const { getAuthorizationUrl: freshGetAuthorizationUrl } = await import('./get-authorization-url.js');
|
|
205
|
+
|
|
206
|
+
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
207
|
+
|
|
208
|
+
const result = await freshGetAuthorizationUrl({});
|
|
209
|
+
|
|
210
|
+
expect(fakeWorkosInstance.pkce.generate).toHaveBeenCalled();
|
|
211
|
+
expect(workos.userManagement.getAuthorizationUrl).toHaveBeenCalledWith(
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
codeChallenge: 'test-code-challenge',
|
|
214
|
+
codeChallengeMethod: 'S256',
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
expect(result.url).toBe('mock-url');
|
|
218
|
+
expect(result.pkceCookieValue).toBeDefined();
|
|
219
|
+
expect(result.pkceCookieValue).not.toBe('');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('returns sealed cookie value for the verifier when PKCE is enabled', async () => {
|
|
223
|
+
process.env.WORKOS_ENABLE_PKCE = 'true';
|
|
224
|
+
|
|
225
|
+
vi.resetModules();
|
|
226
|
+
const { getAuthorizationUrl: freshGetAuthorizationUrl } = await import('./get-authorization-url.js');
|
|
227
|
+
|
|
228
|
+
vi.mocked(workos.userManagement.getAuthorizationUrl).mockReturnValue('mock-url');
|
|
229
|
+
|
|
230
|
+
const result = await freshGetAuthorizationUrl({});
|
|
231
|
+
|
|
232
|
+
// pkceCookieValue should be a sealed (encrypted) string
|
|
233
|
+
expect(typeof result.pkceCookieValue).toBe('string');
|
|
234
|
+
expect(result.pkceCookieValue!.length).toBeGreaterThan(0);
|
|
235
|
+
});
|
|
120
236
|
});
|
|
121
237
|
});
|
|
@@ -1,9 +1,44 @@
|
|
|
1
1
|
import { sealData } from 'iron-session';
|
|
2
2
|
import { headers } from 'next/headers';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
WORKOS_CLAIM_TOKEN,
|
|
5
|
+
WORKOS_CLIENT_ID,
|
|
6
|
+
WORKOS_COOKIE_PASSWORD,
|
|
7
|
+
WORKOS_ENABLE_PKCE,
|
|
8
|
+
WORKOS_REDIRECT_URI,
|
|
9
|
+
} from './env-variables.js';
|
|
4
10
|
import { GetAuthURLOptions, GetAuthURLResult } from './interfaces.js';
|
|
5
11
|
import { getWorkOS } from './workos.js';
|
|
6
12
|
|
|
13
|
+
async function fetchClaimNonce(baseURL: string): Promise<string | null> {
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(`${baseURL}/x/one-shot-environments/claim-nonces`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
client_id: WORKOS_CLIENT_ID,
|
|
20
|
+
claim_token: WORKOS_CLAIM_TOKEN,
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
if (response.status !== 409) {
|
|
25
|
+
console.warn(
|
|
26
|
+
`[authkit-nextjs]: Failed to exchange WORKOS_CLAIM_TOKEN (${response.status}). Try removing WORKOS_CLAIM_TOKEN from your environment variables.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
return data.nonce;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.warn(
|
|
35
|
+
'[authkit-nextjs]: Failed to exchange WORKOS_CLAIM_TOKEN. Try removing WORKOS_CLAIM_TOKEN from your environment variables.',
|
|
36
|
+
error,
|
|
37
|
+
);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
7
42
|
async function getAuthorizationUrl(options: GetAuthURLOptions = {}): Promise<GetAuthURLResult> {
|
|
8
43
|
const { returnPathname, screenHint, organizationId, loginHint, prompt, state: customState } = options;
|
|
9
44
|
let redirectUri = options.redirectUri;
|
|
@@ -16,6 +51,8 @@ async function getAuthorizationUrl(options: GetAuthURLOptions = {}): Promise<Get
|
|
|
16
51
|
? btoa(JSON.stringify({ returnPathname })).replace(/\+/g, '-').replace(/\//g, '_')
|
|
17
52
|
: null;
|
|
18
53
|
|
|
54
|
+
const claimNonce = WORKOS_CLAIM_TOKEN ? await fetchClaimNonce(getWorkOS().baseURL) : null;
|
|
55
|
+
|
|
19
56
|
const finalState =
|
|
20
57
|
internalState && customState ? `${internalState}.${customState}` : internalState || customState || undefined;
|
|
21
58
|
|
|
@@ -28,24 +65,25 @@ async function getAuthorizationUrl(options: GetAuthURLOptions = {}): Promise<Get
|
|
|
28
65
|
organizationId,
|
|
29
66
|
loginHint,
|
|
30
67
|
prompt,
|
|
68
|
+
...(claimNonce && { claimNonce }),
|
|
31
69
|
};
|
|
32
70
|
|
|
33
|
-
if (
|
|
34
|
-
|
|
71
|
+
if (WORKOS_ENABLE_PKCE === 'true') {
|
|
72
|
+
const pkce = await getWorkOS().pkce.generate();
|
|
73
|
+
const pkceCookieValue = await sealData(
|
|
74
|
+
{ codeVerifier: pkce.codeVerifier },
|
|
75
|
+
{ password: WORKOS_COOKIE_PASSWORD, ttl: 600 },
|
|
76
|
+
);
|
|
77
|
+
const url = getWorkOS().userManagement.getAuthorizationUrl({
|
|
78
|
+
...baseOptions,
|
|
79
|
+
codeChallenge: pkce.codeChallenge,
|
|
80
|
+
codeChallengeMethod: pkce.codeChallengeMethod,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return { url, pkceCookieValue };
|
|
35
84
|
}
|
|
36
85
|
|
|
37
|
-
|
|
38
|
-
const pkceCookieValue = await sealData(
|
|
39
|
-
{ codeVerifier: pkce.codeVerifier },
|
|
40
|
-
{ password: WORKOS_COOKIE_PASSWORD, ttl: 600 },
|
|
41
|
-
);
|
|
42
|
-
const url = getWorkOS().userManagement.getAuthorizationUrl({
|
|
43
|
-
...baseOptions,
|
|
44
|
-
codeChallenge: pkce.codeChallenge,
|
|
45
|
-
codeChallengeMethod: pkce.codeChallengeMethod,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return { url, pkceCookieValue };
|
|
86
|
+
return { url: getWorkOS().userManagement.getAuthorizationUrl(baseOptions), pkceCookieValue: undefined };
|
|
49
87
|
}
|
|
50
88
|
|
|
51
89
|
export { getAuthorizationUrl };
|