@workos-inc/authkit-nextjs 4.1.1 → 4.1.3
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/README.md +2 -2
- package/dist/esm/actions.js +12 -1
- package/dist/esm/actions.js.map +1 -1
- package/dist/esm/authkit-callback-route.js +1 -1
- package/dist/esm/authkit-callback-route.js.map +1 -1
- package/dist/esm/components/impersonation.js.map +1 -1
- package/dist/esm/types/actions.d.ts +4 -1
- package/package.json +1 -1
- package/src/actions.spec.ts +54 -4
- package/src/actions.ts +13 -1
- package/src/authkit-callback-route.spec.ts +44 -0
- package/src/authkit-callback-route.ts +1 -3
- package/src/components/impersonation.tsx +1 -2
package/README.md
CHANGED
|
@@ -992,11 +992,11 @@ The `wos-auth-verifier` cookie must survive the round-trip from sign-in initiati
|
|
|
992
992
|
|
|
993
993
|
If the cookie is missing or doesn't match, authentication will fail with one of:
|
|
994
994
|
|
|
995
|
-
- `
|
|
995
|
+
- `Sign-in session could not be verified` — the cookie was not sent back with the callback request. This typically happens when the session has expired or a reverse proxy or CDN strips `Set-Cookie` headers on redirects.
|
|
996
996
|
- `OAuth state mismatch` — the cookie and URL `state` parameter don't match, indicating a possible CSRF attack or cookie corruption.
|
|
997
997
|
|
|
998
998
|
> [!IMPORTANT]
|
|
999
|
-
> **Upgrading to v3:** Previous versions would silently fall back to verifying only the URL `state` parameter when the cookie was missing. This fallback has been removed because it disabled CSRF protection. If you see `
|
|
999
|
+
> **Upgrading to v3:** Previous versions would silently fall back to verifying only the URL `state` parameter when the cookie was missing. This fallback has been removed because it disabled CSRF protection. If you see `Sign-in session could not be verified` errors after upgrading, ensure that `Set-Cookie` headers are propagated on redirects between your application and the user's browser.
|
|
1000
1000
|
|
|
1001
1001
|
### Troubleshooting
|
|
1002
1002
|
|
package/dist/esm/actions.js
CHANGED
|
@@ -25,7 +25,18 @@ export const handleSignOutAction = async ({ returnTo } = {}) => {
|
|
|
25
25
|
await signOut({ returnTo });
|
|
26
26
|
};
|
|
27
27
|
export const getOrganizationAction = async (organizationId) => {
|
|
28
|
-
|
|
28
|
+
// Authorization: only resolve the organization the caller is currently
|
|
29
|
+
// authenticated within. The WorkOS client uses the app's API key, which can
|
|
30
|
+
// read any organization in the environment, so without this check any caller
|
|
31
|
+
// could fetch arbitrary organizations by ID (authorization bypass / IDOR).
|
|
32
|
+
const { user, organizationId: sessionOrganizationId } = await withAuth();
|
|
33
|
+
if (!user || sessionOrganizationId !== organizationId) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
// Return only the fields the client needs. Avoids disclosing the full
|
|
37
|
+
// organization object (metadata, externalId, stripeCustomerId, domains).
|
|
38
|
+
const { id, name } = await getWorkOS().organizations.getOrganization(organizationId);
|
|
39
|
+
return { id, name };
|
|
29
40
|
};
|
|
30
41
|
export const getAuthAction = async (options) => {
|
|
31
42
|
// Never pass ensureSignedIn to withAuth from a server action, because withAuth
|
package/dist/esm/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/actions.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAExE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAOxC;;;;;GAKG;AACH,SAAS,QAAQ,CAAkC,KAAQ;IACzD,6DAA6D;IAC7D,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,EAAE,GAAG,KAAK,CAAC;IAC5C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,IAAI,EAAE;IAC3C,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,EAAE,EAAE,QAAQ,KAA4B,EAAE,EAAE,EAAE;IACpF,MAAM,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,EAAE,cAAsB,EAAE,EAAE;IACpE,OAAO,MAAM,SAAS,EAAE,CAAC,aAAa,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,7 +41,7 @@ export function handleAuth(options = {}) {
|
|
|
41
41
|
const pkceCookie = request.cookies.get(pkceCookieName)?.value ?? request.cookies.get(PKCE_COOKIE_NAME)?.value;
|
|
42
42
|
// CSRF verification: both channels (cookie + URL state) must be present and match
|
|
43
43
|
if (!pkceCookie) {
|
|
44
|
-
throw new Error('
|
|
44
|
+
throw new Error('Sign-in session could not be verified. Please try signing in again.');
|
|
45
45
|
}
|
|
46
46
|
if (state !== pkceCookie) {
|
|
47
47
|
throw new Error('OAuth state mismatch');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authkit-callback-route.js","sourceRoot":"","sources":["../../src/authkit-callback-route.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AACrG,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AACxG,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,SAAS,cAAc,CAAC,OAAgB;IACtC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9B,yBAAyB,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,UAA6B,EAAE;IACxD,MAAM,EAAE,cAAc,EAAE,oBAAoB,GAAG,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAE5F,iDAAiD;IACjD,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,UAAU,GAAG,CAAC,OAAoB;QAC5C,iFAAiF;QACjF,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAE3D,+BAA+B;QAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjD,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEnD,qEAAqE;QACrE,sEAAsE;QACtE,0CAA0C;QAC1C,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACrD,CAAC;YAED,oEAAoE;YACpE,uEAAuE;YACvE,sEAAsE;YACtE,oEAAoE;YACpE,6CAA6C;YAC7C,MAAM,cAAc,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;YACxD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,KAAK,CAAC;YAE9G,kFAAkF;YAClF,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"authkit-callback-route.js","sourceRoot":"","sources":["../../src/authkit-callback-route.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"impersonation.js","sourceRoot":"","sources":["../../../src/components/impersonation.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"impersonation.js","sourceRoot":"","sources":["../../../src/components/impersonation.tsx"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,10 @@ export declare const checkSessionAction: () => Promise<boolean>;
|
|
|
12
12
|
export declare const handleSignOutAction: ({ returnTo }?: {
|
|
13
13
|
returnTo?: string;
|
|
14
14
|
}) => Promise<void>;
|
|
15
|
-
export declare const getOrganizationAction: (organizationId: string) => Promise<
|
|
15
|
+
export declare const getOrganizationAction: (organizationId: string) => Promise<{
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
} | null>;
|
|
16
19
|
export declare const getAuthAction: (options?: {
|
|
17
20
|
ensureSignedIn?: boolean;
|
|
18
21
|
}) => Promise<Omit<UserInfo | NoUserInfo, "accessToken"> | {
|
package/package.json
CHANGED
package/src/actions.spec.ts
CHANGED
|
@@ -67,10 +67,60 @@ describe('actions', () => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
describe('getOrganizationAction', () => {
|
|
70
|
-
it('should return organization details', async () => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
it('should return organization details for the current session organization', async () => {
|
|
71
|
+
vi.mocked(withAuth).mockResolvedValue({
|
|
72
|
+
user: 'testUser' as never,
|
|
73
|
+
sessionId: 'session_123',
|
|
74
|
+
accessToken: 'access_token',
|
|
75
|
+
organizationId: 'org_123',
|
|
76
|
+
});
|
|
77
|
+
const result = await getOrganizationAction('org_123');
|
|
78
|
+
expect(workos.organizations.getOrganization).toHaveBeenCalledWith('org_123');
|
|
79
|
+
expect(result).toEqual({ id: 'org_123', name: 'Test Org' });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('denies fetching an organization the user is not authenticated within', async () => {
|
|
83
|
+
// Session is scoped to org_123; the caller requests a different org.
|
|
84
|
+
vi.mocked(withAuth).mockResolvedValue({
|
|
85
|
+
user: 'testUser' as never,
|
|
86
|
+
sessionId: 'session_123',
|
|
87
|
+
accessToken: 'access_token',
|
|
88
|
+
organizationId: 'org_123',
|
|
89
|
+
});
|
|
90
|
+
const result = await getOrganizationAction('org_456');
|
|
91
|
+
expect(result).toBeNull();
|
|
92
|
+
expect(workos.organizations.getOrganization).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns null when there is no authenticated user', async () => {
|
|
96
|
+
vi.mocked(withAuth).mockResolvedValue({ user: null } as never);
|
|
97
|
+
const result = await getOrganizationAction('org_123');
|
|
98
|
+
expect(result).toBeNull();
|
|
99
|
+
expect(workos.organizations.getOrganization).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('returns only id and name, never the full organization object', async () => {
|
|
103
|
+
vi.mocked(withAuth).mockResolvedValue({
|
|
104
|
+
user: 'testUser' as never,
|
|
105
|
+
sessionId: 'session_123',
|
|
106
|
+
accessToken: 'access_token',
|
|
107
|
+
organizationId: 'org_123',
|
|
108
|
+
});
|
|
109
|
+
vi.mocked(workos.organizations.getOrganization).mockResolvedValueOnce({
|
|
110
|
+
object: 'organization',
|
|
111
|
+
id: 'org_123',
|
|
112
|
+
name: 'Test Org',
|
|
113
|
+
allowProfilesOutsideOrganization: false,
|
|
114
|
+
domains: [],
|
|
115
|
+
stripeCustomerId: 'cus_should_not_leak',
|
|
116
|
+
externalId: 'ext_should_not_leak',
|
|
117
|
+
metadata: { secret: 'should_not_leak' },
|
|
118
|
+
createdAt: '2020-01-01T00:00:00.000Z',
|
|
119
|
+
updatedAt: '2020-01-01T00:00:00.000Z',
|
|
120
|
+
} as never);
|
|
121
|
+
|
|
122
|
+
const result = await getOrganizationAction('org_123');
|
|
123
|
+
|
|
74
124
|
expect(result).toEqual({ id: 'org_123', name: 'Test Org' });
|
|
75
125
|
});
|
|
76
126
|
});
|
package/src/actions.ts
CHANGED
|
@@ -36,7 +36,19 @@ export const handleSignOutAction = async ({ returnTo }: { returnTo?: string } =
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export const getOrganizationAction = async (organizationId: string) => {
|
|
39
|
-
|
|
39
|
+
// Authorization: only resolve the organization the caller is currently
|
|
40
|
+
// authenticated within. The WorkOS client uses the app's API key, which can
|
|
41
|
+
// read any organization in the environment, so without this check any caller
|
|
42
|
+
// could fetch arbitrary organizations by ID (authorization bypass / IDOR).
|
|
43
|
+
const { user, organizationId: sessionOrganizationId } = await withAuth();
|
|
44
|
+
if (!user || sessionOrganizationId !== organizationId) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Return only the fields the client needs. Avoids disclosing the full
|
|
49
|
+
// organization object (metadata, externalId, stripeCustomerId, domains).
|
|
50
|
+
const { id, name } = await getWorkOS().organizations.getOrganization(organizationId);
|
|
51
|
+
return { id, name };
|
|
40
52
|
};
|
|
41
53
|
|
|
42
54
|
export const getAuthAction = async (options?: { ensureSignedIn?: boolean }) => {
|
|
@@ -233,6 +233,50 @@ describe('authkit-callback-route', () => {
|
|
|
233
233
|
expect(location).not.toContain('https://example.com/invite');
|
|
234
234
|
});
|
|
235
235
|
|
|
236
|
+
// Regression coverage for the open-redirect / javascript:-URI class reported
|
|
237
|
+
// against the `state` param. `returnPathname` is read only from the sealed
|
|
238
|
+
// (tamper-proof) PKCE cookie and the callback copies only the pathname +
|
|
239
|
+
// search onto the app's own origin, so a hostile value can never change the
|
|
240
|
+
// redirect's scheme or host. These tests pin that invariant so a refactor
|
|
241
|
+
// that started honoring the full URL would fail loudly.
|
|
242
|
+
describe('returnPathname is neutralized to the app origin', () => {
|
|
243
|
+
const appOrigin = 'http://example.com';
|
|
244
|
+
const hostileReturnPathnames = [
|
|
245
|
+
'javascript:alert(document.domain)',
|
|
246
|
+
'data:text/html,<script>alert(1)</script>',
|
|
247
|
+
'https://evil.com/phishing',
|
|
248
|
+
'//evil.com/phishing',
|
|
249
|
+
'/\\evil.com/phishing',
|
|
250
|
+
'https:/evil.com',
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
it.each(hostileReturnPathnames)('keeps the redirect same-origin for %s', async (returnPathname) => {
|
|
254
|
+
vi.mocked(workos.userManagement.authenticateWithCode).mockResolvedValue(mockAuthResponse);
|
|
255
|
+
|
|
256
|
+
const sealedState = await setAuthCookie(request, {
|
|
257
|
+
nonce: 'foo',
|
|
258
|
+
codeVerifier: 'test-verifier',
|
|
259
|
+
returnPathname,
|
|
260
|
+
});
|
|
261
|
+
request.nextUrl.searchParams.set('code', 'test-code');
|
|
262
|
+
request.nextUrl.searchParams.set('state', sealedState);
|
|
263
|
+
|
|
264
|
+
const handler = handleAuth();
|
|
265
|
+
const response = await handler(request);
|
|
266
|
+
|
|
267
|
+
const location = response.headers.get('Location');
|
|
268
|
+
expect(location).not.toBeNull();
|
|
269
|
+
const redirectUrl = new URL(location!);
|
|
270
|
+
|
|
271
|
+
// The scheme is never javascript:/data: and the host is never the
|
|
272
|
+
// attacker's — a hostile value can only ever become a path on our own
|
|
273
|
+
// origin (e.g. "https:/evil.com" lands at http://example.com/evil.com).
|
|
274
|
+
expect(redirectUrl.protocol).toBe('http:');
|
|
275
|
+
expect(redirectUrl.origin).toBe(appOrigin);
|
|
276
|
+
expect(redirectUrl.hostname).toBe('example.com');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
236
280
|
it('should use Response if NextResponse.redirect is not available', async () => {
|
|
237
281
|
const originalRedirect = NextResponse.redirect;
|
|
238
282
|
(NextResponse as Partial<typeof NextResponse>).redirect = undefined;
|
|
@@ -50,9 +50,7 @@ export function handleAuth(options: HandleAuthOptions = {}) {
|
|
|
50
50
|
|
|
51
51
|
// CSRF verification: both channels (cookie + URL state) must be present and match
|
|
52
52
|
if (!pkceCookie) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
'Auth cookie missing — cannot verify OAuth state. Ensure Set-Cookie headers are propagated on redirects.',
|
|
55
|
-
);
|
|
53
|
+
throw new Error('Sign-in session could not be verified. Please try signing in again.');
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
if (state !== pkceCookie) {
|
|
@@ -4,7 +4,6 @@ import * as React from 'react';
|
|
|
4
4
|
import { Button } from './button.js';
|
|
5
5
|
import { MinMaxButton } from './min-max-button.js';
|
|
6
6
|
import { getOrganizationAction, handleSignOutAction } from '../actions.js';
|
|
7
|
-
import type { Organization } from '@workos-inc/node';
|
|
8
7
|
import { useAuth } from './authkit-provider.js';
|
|
9
8
|
|
|
10
9
|
interface ImpersonationProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
@@ -15,7 +14,7 @@ interface ImpersonationProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
15
14
|
export function Impersonation({ side = 'bottom', returnTo, ...props }: ImpersonationProps) {
|
|
16
15
|
const { user, impersonator, organizationId } = useAuth();
|
|
17
16
|
|
|
18
|
-
const [organization, setOrganization] = React.useState<
|
|
17
|
+
const [organization, setOrganization] = React.useState<{ id: string; name: string } | null>(null);
|
|
19
18
|
|
|
20
19
|
React.useEffect(() => {
|
|
21
20
|
if (!organizationId || !impersonator || !user) return;
|