@workos-inc/authkit-nextjs 0.17.2 → 1.0.1

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.
Files changed (63) hide show
  1. package/README.md +102 -25
  2. package/dist/esm/actions.js +11 -0
  3. package/dist/esm/actions.js.map +1 -1
  4. package/dist/esm/components/authkit-provider.js +128 -0
  5. package/dist/esm/components/authkit-provider.js.map +1 -0
  6. package/dist/esm/components/button.js.map +1 -0
  7. package/dist/esm/{impersonation.js → components/impersonation.js} +12 -7
  8. package/dist/esm/components/impersonation.js.map +1 -0
  9. package/dist/esm/components/index.js +4 -0
  10. package/dist/esm/components/index.js.map +1 -0
  11. package/dist/esm/components/min-max-button.js.map +1 -0
  12. package/dist/esm/cookie.js +15 -12
  13. package/dist/esm/cookie.js.map +1 -1
  14. package/dist/esm/env-variables.js +5 -2
  15. package/dist/esm/env-variables.js.map +1 -1
  16. package/dist/esm/index.js +4 -8
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/middleware.js +5 -2
  19. package/dist/esm/middleware.js.map +1 -1
  20. package/dist/esm/session.js +117 -93
  21. package/dist/esm/session.js.map +1 -1
  22. package/dist/esm/types/actions.d.ts +6 -0
  23. package/dist/esm/types/components/authkit-provider.d.ts +33 -0
  24. package/dist/esm/types/{impersonation.d.ts → components/impersonation.d.ts} +1 -1
  25. package/dist/esm/types/components/index.d.ts +3 -0
  26. package/dist/esm/types/cookie.d.ts +4 -0
  27. package/dist/esm/types/env-variables.d.ts +2 -2
  28. package/dist/esm/types/index.d.ts +3 -5
  29. package/dist/esm/types/interfaces.d.ts +11 -0
  30. package/dist/esm/types/middleware.d.ts +3 -2
  31. package/dist/esm/types/session.d.ts +5 -28
  32. package/dist/esm/types/utils.d.ts +1 -1
  33. package/dist/esm/types/workos.d.ts +1 -1
  34. package/dist/esm/utils.js +5 -3
  35. package/dist/esm/utils.js.map +1 -1
  36. package/dist/esm/workos.js +1 -1
  37. package/dist/esm/workos.js.map +1 -1
  38. package/package.json +11 -1
  39. package/src/actions.ts +20 -0
  40. package/src/components/authkit-provider.tsx +169 -0
  41. package/src/{impersonation.tsx → components/impersonation.tsx} +14 -7
  42. package/src/components/index.ts +4 -0
  43. package/src/cookie.ts +33 -12
  44. package/src/env-variables.ts +7 -2
  45. package/src/index.ts +3 -8
  46. package/src/interfaces.ts +13 -0
  47. package/src/middleware.ts +8 -4
  48. package/src/session.ts +159 -110
  49. package/src/utils.ts +6 -3
  50. package/src/workos.ts +1 -1
  51. package/dist/esm/authkit-provider.js +0 -52
  52. package/dist/esm/authkit-provider.js.map +0 -1
  53. package/dist/esm/button.js.map +0 -1
  54. package/dist/esm/impersonation.js.map +0 -1
  55. package/dist/esm/min-max-button.js.map +0 -1
  56. package/dist/esm/types/authkit-provider.d.ts +0 -11
  57. package/src/authkit-provider.tsx +0 -66
  58. /package/dist/esm/{button.js → components/button.js} +0 -0
  59. /package/dist/esm/{min-max-button.js → components/min-max-button.js} +0 -0
  60. /package/dist/esm/types/{button.d.ts → components/button.d.ts} +0 -0
  61. /package/dist/esm/types/{min-max-button.d.ts → components/min-max-button.d.ts} +0 -0
  62. /package/src/{button.tsx → components/button.tsx} +0 -0
  63. /package/src/{min-max-button.tsx → components/min-max-button.tsx} +0 -0
@@ -0,0 +1,169 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
4
+ import { checkSessionAction, getAuthAction, refreshAuthAction } from '../actions.js';
5
+ import type { Impersonator, User } from '@workos-inc/node';
6
+
7
+ type AuthContextType = {
8
+ user: User | null;
9
+ sessionId: string | undefined;
10
+ organizationId: string | undefined;
11
+ role: string | undefined;
12
+ permissions: string[] | undefined;
13
+ entitlements: string[] | undefined;
14
+ impersonator: Impersonator | undefined;
15
+ accessToken: string | undefined;
16
+ loading: boolean;
17
+ getAuth: (options?: { ensureSignedIn?: boolean }) => Promise<void>;
18
+ refreshAuth: (options?: { ensureSignedIn?: boolean; organizationId?: string }) => Promise<void | { error: string }>;
19
+ };
20
+
21
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
22
+
23
+ interface AuthKitProviderProps {
24
+ children: ReactNode;
25
+ /**
26
+ * Customize what happens when a session is expired. By default,the entire page will be reloaded.
27
+ * You can also pass this as `false` to disable the expired session checks.
28
+ */
29
+ onSessionExpired?: false | (() => void);
30
+ }
31
+
32
+ export const AuthKitProvider = ({ children, onSessionExpired }: AuthKitProviderProps) => {
33
+ const [user, setUser] = useState<User | null>(null);
34
+ const [sessionId, setSessionId] = useState<string | undefined>(undefined);
35
+ const [organizationId, setOrganizationId] = useState<string | undefined>(undefined);
36
+ const [role, setRole] = useState<string | undefined>(undefined);
37
+ const [permissions, setPermissions] = useState<string[] | undefined>(undefined);
38
+ const [entitlements, setEntitlements] = useState<string[] | undefined>(undefined);
39
+ const [impersonator, setImpersonator] = useState<Impersonator | undefined>(undefined);
40
+ const [accessToken, setAccessToken] = useState<string | undefined>(undefined);
41
+ const [loading, setLoading] = useState(true);
42
+
43
+ const getAuth = async ({ ensureSignedIn = false }: { ensureSignedIn?: boolean } = {}) => {
44
+ try {
45
+ const auth = await getAuthAction(ensureSignedIn);
46
+ setUser(auth.user);
47
+ setSessionId(auth.sessionId);
48
+ setOrganizationId(auth.organizationId);
49
+ setRole(auth.role);
50
+ setPermissions(auth.permissions);
51
+ setEntitlements(auth.entitlements);
52
+ setImpersonator(auth.impersonator);
53
+ setAccessToken(auth.accessToken);
54
+ } catch (error) {
55
+ setUser(null);
56
+ setSessionId(undefined);
57
+ setOrganizationId(undefined);
58
+ setRole(undefined);
59
+ setPermissions(undefined);
60
+ setEntitlements(undefined);
61
+ setImpersonator(undefined);
62
+ setAccessToken(undefined);
63
+ } finally {
64
+ setLoading(false);
65
+ }
66
+ };
67
+
68
+ const refreshAuth = async ({
69
+ ensureSignedIn = false,
70
+ organizationId,
71
+ }: { ensureSignedIn?: boolean; organizationId?: string } = {}) => {
72
+ try {
73
+ setLoading(true);
74
+ const auth = await refreshAuthAction({ ensureSignedIn, organizationId });
75
+
76
+ setUser(auth.user);
77
+ setSessionId(auth.sessionId);
78
+ setOrganizationId(auth.organizationId);
79
+ setRole(auth.role);
80
+ setPermissions(auth.permissions);
81
+ setEntitlements(auth.entitlements);
82
+ setImpersonator(auth.impersonator);
83
+ setAccessToken(auth.accessToken);
84
+ } catch (error) {
85
+ return error instanceof Error ? { error: error.message } : { error: String(error) };
86
+ } finally {
87
+ setLoading(false);
88
+ }
89
+ };
90
+
91
+ useEffect(() => {
92
+ getAuth();
93
+
94
+ // Return early if the session expired checks are disabled.
95
+ if (onSessionExpired === false) {
96
+ return;
97
+ }
98
+
99
+ let visibilityChangedCalled = false;
100
+
101
+ const handleVisibilityChange = async () => {
102
+ if (visibilityChangedCalled) {
103
+ return;
104
+ }
105
+
106
+ // In the case where we're using middleware auth mode, a user that has signed out in a different tab
107
+ // will run into an issue if they attempt to hit a server action in the original tab.
108
+ // This will force a refresh of the page in that case, which will redirect them to the sign-in page.
109
+ if (document.visibilityState === 'visible') {
110
+ visibilityChangedCalled = true;
111
+
112
+ try {
113
+ const hasSession = await checkSessionAction();
114
+ if (!hasSession) {
115
+ throw new Error('Session expired');
116
+ }
117
+ } catch (error) {
118
+ // 'Failed to fetch' is the error we are looking for if the action fails
119
+ // If any other error happens, for other reasons, we should not reload the page
120
+ if (error instanceof Error && error.message.includes('Failed to fetch')) {
121
+ if (onSessionExpired) {
122
+ onSessionExpired();
123
+ } else {
124
+ window.location.reload();
125
+ }
126
+ }
127
+ } finally {
128
+ visibilityChangedCalled = false;
129
+ }
130
+ }
131
+ };
132
+
133
+ window.addEventListener('visibilitychange', handleVisibilityChange);
134
+ window.addEventListener('focus', handleVisibilityChange);
135
+
136
+ return () => {
137
+ window.removeEventListener('focus', handleVisibilityChange);
138
+ window.removeEventListener('visibilitychange', handleVisibilityChange);
139
+ };
140
+ }, [onSessionExpired]);
141
+
142
+ return (
143
+ <AuthContext.Provider
144
+ value={{
145
+ user,
146
+ sessionId,
147
+ organizationId,
148
+ role,
149
+ permissions,
150
+ entitlements,
151
+ impersonator,
152
+ accessToken,
153
+ loading,
154
+ getAuth,
155
+ refreshAuth,
156
+ }}
157
+ >
158
+ {children}
159
+ </AuthContext.Provider>
160
+ );
161
+ };
162
+
163
+ export function useAuth() {
164
+ const context = useContext(AuthContext);
165
+ if (!context) {
166
+ throw new Error('useAuth must be used within an AuthKitProvider');
167
+ }
168
+ return context;
169
+ }
@@ -1,20 +1,27 @@
1
+ 'use client';
2
+
1
3
  import * as React from 'react';
2
- import { withAuth } from './session.js';
3
- import { workos } from './workos.js';
4
4
  import { Button } from './button.js';
5
5
  import { MinMaxButton } from './min-max-button.js';
6
- import { handleSignOutAction } from './actions.js';
6
+ import { getOrganizationAction, handleSignOutAction } from '../actions.js';
7
+ import type { Organization } from '@workos-inc/node';
8
+ import { useAuth } from './authkit-provider.js';
7
9
 
8
10
  interface ImpersonationProps extends React.ComponentPropsWithoutRef<'div'> {
9
11
  side?: 'top' | 'bottom';
10
12
  }
11
13
 
12
- export async function Impersonation({ side = 'bottom', ...props }: ImpersonationProps) {
13
- const { impersonator, user, organizationId } = await withAuth();
14
+ export function Impersonation({ side = 'bottom', ...props }: ImpersonationProps) {
15
+ const { user, impersonator, organizationId, loading } = useAuth();
16
+
17
+ const [organization, setOrganization] = React.useState<Organization | null>(null);
14
18
 
15
- if (!impersonator) return null;
19
+ React.useEffect(() => {
20
+ if (!organizationId) return;
21
+ getOrganizationAction(organizationId).then(setOrganization);
22
+ }, [organizationId]);
16
23
 
17
- const organization = organizationId ? await workos.organizations.getOrganization(organizationId) : null;
24
+ if (loading || !impersonator || !user) return null;
18
25
 
19
26
  return (
20
27
  <div
@@ -0,0 +1,4 @@
1
+ import { Impersonation } from './impersonation.js';
2
+ import { AuthKitProvider, useAuth } from './authkit-provider.js';
3
+
4
+ export { Impersonation, AuthKitProvider, useAuth };
package/src/cookie.ts CHANGED
@@ -1,18 +1,39 @@
1
1
  import { WORKOS_REDIRECT_URI, WORKOS_COOKIE_MAX_AGE, WORKOS_COOKIE_DOMAIN } from './env-variables.js';
2
2
  import { CookieOptions } from './interfaces.js';
3
3
 
4
- export function getCookieOptions(redirectUri?: string | null): CookieOptions {
4
+ export function getCookieOptions(): CookieOptions;
5
+ export function getCookieOptions(redirectUri?: string | null): CookieOptions;
6
+ export function getCookieOptions(redirectUri: string | null | undefined, asString: true, expired?: boolean): string;
7
+ export function getCookieOptions(
8
+ redirectUri: string | null | undefined,
9
+ asString: false,
10
+ expired?: boolean,
11
+ ): CookieOptions;
12
+ export function getCookieOptions(
13
+ redirectUri?: string | null,
14
+ asString?: boolean,
15
+ expired?: boolean,
16
+ ): CookieOptions | string;
17
+ export function getCookieOptions(
18
+ redirectUri?: string | null,
19
+ asString: boolean = false,
20
+ expired: boolean = false,
21
+ ): CookieOptions | string {
5
22
  const url = new URL(redirectUri || WORKOS_REDIRECT_URI);
6
23
 
7
- return {
8
- path: '/',
9
- httpOnly: true,
10
- secure: url.protocol === 'https:',
11
- sameSite: 'lax' as const,
12
- // Defaults to 400 days, the maximum allowed by Chrome
13
- // It's fine to have a long cookie expiry date as the access/refresh tokens
14
- // act as the actual time-limited aspects of the session.
15
- maxAge: WORKOS_COOKIE_MAX_AGE ? parseInt(WORKOS_COOKIE_MAX_AGE, 10) : 60 * 60 * 24 * 400,
16
- domain: WORKOS_COOKIE_DOMAIN,
17
- };
24
+ const maxAge = expired ? 0 : WORKOS_COOKIE_MAX_AGE ? parseInt(WORKOS_COOKIE_MAX_AGE, 10) : 60 * 60 * 24 * 400;
25
+
26
+ return asString
27
+ ? `Path=/; HttpOnly; Secure=${url.protocol === 'https:'}; SameSite="Lax"; Max-Age=${maxAge}; Domain=${WORKOS_COOKIE_DOMAIN || ''}`
28
+ : {
29
+ path: '/',
30
+ httpOnly: true,
31
+ secure: url.protocol === 'https:',
32
+ sameSite: 'lax' as const,
33
+ // Defaults to 400 days, the maximum allowed by Chrome
34
+ // It's fine to have a long cookie expiry date as the access/refresh tokens
35
+ // act as the actual time-limited aspects of the session.
36
+ maxAge,
37
+ domain: WORKOS_COOKIE_DOMAIN || '',
38
+ };
18
39
  }
@@ -1,15 +1,20 @@
1
+ /* istanbul ignore file */
2
+
1
3
  function getEnvVariable(name: string): string | undefined {
2
4
  return process.env[name];
3
5
  }
4
6
 
7
+ // Optional env variables
5
8
  const WORKOS_API_HOSTNAME = getEnvVariable('WORKOS_API_HOSTNAME');
6
9
  const WORKOS_API_HTTPS = getEnvVariable('WORKOS_API_HTTPS');
7
- const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY') ?? '';
8
10
  const WORKOS_API_PORT = getEnvVariable('WORKOS_API_PORT');
9
- const WORKOS_CLIENT_ID = getEnvVariable('WORKOS_CLIENT_ID') ?? '';
10
11
  const WORKOS_COOKIE_DOMAIN = getEnvVariable('WORKOS_COOKIE_DOMAIN');
11
12
  const WORKOS_COOKIE_MAX_AGE = getEnvVariable('WORKOS_COOKIE_MAX_AGE');
12
13
  const WORKOS_COOKIE_NAME = getEnvVariable('WORKOS_COOKIE_NAME');
14
+
15
+ // Required env variables
16
+ const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY') ?? '';
17
+ const WORKOS_CLIENT_ID = getEnvVariable('WORKOS_CLIENT_ID') ?? '';
13
18
  const WORKOS_COOKIE_PASSWORD = getEnvVariable('WORKOS_COOKIE_PASSWORD') ?? '';
14
19
  const WORKOS_REDIRECT_URI = process.env.NEXT_PUBLIC_WORKOS_REDIRECT_URI ?? '';
15
20
 
package/src/index.ts CHANGED
@@ -1,22 +1,17 @@
1
1
  import { handleAuth } from './authkit-callback-route.js';
2
- import { authkitMiddleware } from './middleware.js';
3
- import { withAuth, refreshSession, getSession } from './session.js';
2
+ import { authkit, authkitMiddleware } from './middleware.js';
3
+ import { withAuth, refreshSession } from './session.js';
4
4
  import { getSignInUrl, getSignUpUrl, signOut } from './auth.js';
5
- import { Impersonation } from './impersonation.js';
6
- import { AuthKitProvider } from './authkit-provider.js';
7
5
 
8
6
  export {
9
7
  handleAuth,
10
8
  //
11
9
  authkitMiddleware,
12
- getSession,
10
+ authkit,
13
11
  //
14
12
  getSignInUrl,
15
13
  getSignUpUrl,
16
14
  withAuth,
17
15
  refreshSession,
18
16
  signOut,
19
- //
20
- Impersonation,
21
- AuthKitProvider,
22
17
  };
package/src/interfaces.ts CHANGED
@@ -37,6 +37,7 @@ export interface NoUserInfo {
37
37
  organizationId?: undefined;
38
38
  role?: undefined;
39
39
  permissions?: undefined;
40
+ entitlements?: undefined;
40
41
  impersonator?: undefined;
41
42
  accessToken?: undefined;
42
43
  }
@@ -68,6 +69,18 @@ export interface AuthkitMiddlewareOptions {
68
69
  signUpPaths?: string[];
69
70
  }
70
71
 
72
+ export interface AuthkitOptions {
73
+ debug?: boolean;
74
+ redirectUri?: string;
75
+ screenHint?: 'sign-up' | 'sign-in';
76
+ }
77
+
78
+ export interface AuthkitResponse {
79
+ session: UserInfo | NoUserInfo;
80
+ headers: Headers;
81
+ authorizationUrl?: string;
82
+ }
83
+
71
84
  export interface CookieOptions {
72
85
  path: '/';
73
86
  httpOnly: true;
package/src/middleware.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { NextMiddleware } from 'next/server';
2
- import { updateSession } from './session.js';
3
- import { AuthkitMiddlewareOptions } from './interfaces.js';
1
+ import { NextMiddleware, NextRequest } from 'next/server';
2
+ import { updateSessionMiddleware, updateSession } from './session.js';
3
+ import { AuthkitMiddlewareOptions, AuthkitOptions, AuthkitResponse } from './interfaces.js';
4
4
  import { WORKOS_REDIRECT_URI } from './env-variables.js';
5
5
 
6
6
  export function authkitMiddleware({
@@ -10,6 +10,10 @@ export function authkitMiddleware({
10
10
  signUpPaths = [],
11
11
  }: AuthkitMiddlewareOptions = {}): NextMiddleware {
12
12
  return function (request) {
13
- return updateSession(request, debug, middlewareAuth, redirectUri, signUpPaths);
13
+ return updateSessionMiddleware(request, debug, middlewareAuth, redirectUri, signUpPaths);
14
14
  };
15
15
  }
16
+
17
+ export async function authkit(request: NextRequest, options: AuthkitOptions = {}): Promise<AuthkitResponse> {
18
+ return await updateSession(request, options);
19
+ }