@workos-inc/authkit-nextjs 2.4.2 → 2.4.4
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 +10 -2
- package/dist/esm/components/authkit-provider.js +9 -9
- package/dist/esm/components/authkit-provider.js.map +1 -1
- package/dist/esm/components/useAccessToken.js +37 -20
- package/dist/esm/components/useAccessToken.js.map +1 -1
- package/dist/esm/components/useTokenClaims.js +2 -2
- package/dist/esm/components/useTokenClaims.js.map +1 -1
- package/dist/esm/jwt.js +32 -0
- package/dist/esm/jwt.js.map +1 -0
- package/dist/esm/types/components/useTokenClaims.d.ts +1 -3
- package/dist/esm/types/jwt.d.ts +71 -0
- package/dist/esm/types/workos.d.ts +1 -1
- package/dist/esm/workos.js +1 -1
- package/package.json +1 -1
- package/src/components/authkit-provider.tsx +43 -40
- package/src/components/useAccessToken.ts +46 -21
- package/src/components/useTokenClaims.ts +2 -4
- package/src/jwt.ts +106 -0
- package/src/workos.ts +1 -1
package/README.md
CHANGED
|
@@ -102,12 +102,20 @@ export const GET = handleAuth({
|
|
|
102
102
|
});
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
When running in environments like Docker, set the `baseURL` explicitly to ensure the redirects point to the correct location.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
export const GET = handleAuth({
|
|
109
|
+
baseURL: 'http://localhost:3000',
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
105
113
|
`handleAuth` can be used with the following options.
|
|
106
114
|
|
|
107
115
|
| Option | Default | Description |
|
|
108
116
|
| ---------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
109
117
|
| `returnPathname` | `/` | The pathname to redirect the user to after signing in |
|
|
110
|
-
| `baseURL` | `undefined` | The base URL to use for the redirect URI instead of the one in the request.
|
|
118
|
+
| `baseURL` | `undefined` | The base URL to use for the redirect URI instead of the one in the request. **Required** if the app is being run in a container like docker where the hostname can be different from the one in the request |
|
|
111
119
|
| `onSuccess` | `undefined` | A function that receives successful authentication data and can be used for side-effects like persisting tokens |
|
|
112
120
|
| `onError` | `undefined` | A function that can receive the error and the request and handle the error in its own way. |
|
|
113
121
|
|
|
@@ -429,7 +437,7 @@ If you don't want to use `authkitMiddleware` and instead want to compose your ow
|
|
|
429
437
|
export default async function middleware(request: NextRequest) {
|
|
430
438
|
// Perform logic before or after AuthKit
|
|
431
439
|
|
|
432
|
-
// Auth object contains the session, response headers and an
|
|
440
|
+
// Auth object contains the session, response headers and an authorization URL in the case that the session isn't valid
|
|
433
441
|
// This method will automatically handle setting the cookie and refreshing the session
|
|
434
442
|
const { session, headers, authorizationUrl } = await authkit(request, {
|
|
435
443
|
debug: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
|
3
3
|
import { checkSessionAction, getAuthAction, handleSignOutAction, refreshAuthAction, switchToOrganizationAction, } from '../actions.js';
|
|
4
4
|
const AuthContext = createContext(undefined);
|
|
5
5
|
export const AuthKitProvider = ({ children, onSessionExpired }) => {
|
|
@@ -12,7 +12,7 @@ export const AuthKitProvider = ({ children, onSessionExpired }) => {
|
|
|
12
12
|
const [featureFlags, setFeatureFlags] = useState(undefined);
|
|
13
13
|
const [impersonator, setImpersonator] = useState(undefined);
|
|
14
14
|
const [loading, setLoading] = useState(true);
|
|
15
|
-
const getAuth = async ({ ensureSignedIn = false } = {}) => {
|
|
15
|
+
const getAuth = useCallback(async ({ ensureSignedIn = false } = {}) => {
|
|
16
16
|
setLoading(true);
|
|
17
17
|
try {
|
|
18
18
|
const auth = await getAuthAction({ ensureSignedIn });
|
|
@@ -38,8 +38,8 @@ export const AuthKitProvider = ({ children, onSessionExpired }) => {
|
|
|
38
38
|
finally {
|
|
39
39
|
setLoading(false);
|
|
40
40
|
}
|
|
41
|
-
};
|
|
42
|
-
const switchToOrganization = async (organizationId, options = {}) => {
|
|
41
|
+
}, []);
|
|
42
|
+
const switchToOrganization = useCallback(async (organizationId, options = {}) => {
|
|
43
43
|
const opts = { revalidationStrategy: 'none', ...options };
|
|
44
44
|
const result = await switchToOrganizationAction(organizationId, {
|
|
45
45
|
revalidationStrategy: 'none',
|
|
@@ -49,8 +49,8 @@ export const AuthKitProvider = ({ children, onSessionExpired }) => {
|
|
|
49
49
|
await getAuth({ ensureSignedIn: true });
|
|
50
50
|
}
|
|
51
51
|
return result;
|
|
52
|
-
};
|
|
53
|
-
const refreshAuth = async ({ ensureSignedIn = false, organizationId
|
|
52
|
+
}, []);
|
|
53
|
+
const refreshAuth = useCallback(async ({ ensureSignedIn = false, organizationId } = {}) => {
|
|
54
54
|
try {
|
|
55
55
|
setLoading(true);
|
|
56
56
|
const auth = await refreshAuthAction({ ensureSignedIn, organizationId });
|
|
@@ -69,10 +69,10 @@ export const AuthKitProvider = ({ children, onSessionExpired }) => {
|
|
|
69
69
|
finally {
|
|
70
70
|
setLoading(false);
|
|
71
71
|
}
|
|
72
|
-
};
|
|
73
|
-
const signOut = async ({ returnTo } = {}) => {
|
|
72
|
+
}, []);
|
|
73
|
+
const signOut = useCallback(async ({ returnTo } = {}) => {
|
|
74
74
|
await handleSignOutAction({ returnTo });
|
|
75
|
-
};
|
|
75
|
+
}, []);
|
|
76
76
|
useEffect(() => {
|
|
77
77
|
getAuth();
|
|
78
78
|
// Return early if the session expired checks are disabled.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authkit-provider.js","sourceRoot":"","sources":["../../../src/components/authkit-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,EAAE,EAAE,aAAa,EAAa,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"authkit-provider.js","sourceRoot":"","sources":["../../../src/components/authkit-provider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
2
2
|
import { getAccessTokenAction, refreshAccessTokenAction } from '../actions.js';
|
|
3
3
|
import { useAuth } from './authkit-provider.js';
|
|
4
|
+
import { decodeJwt } from '../jwt.js';
|
|
4
5
|
const TOKEN_EXPIRY_BUFFER_SECONDS = 60;
|
|
5
6
|
const MIN_REFRESH_DELAY_SECONDS = 15; // minimum delay before refreshing token
|
|
6
7
|
const MAX_REFRESH_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
|
|
@@ -10,7 +11,7 @@ function tokenReducer(state, action) {
|
|
|
10
11
|
case 'FETCH_START':
|
|
11
12
|
return { ...state, loading: true, error: null };
|
|
12
13
|
case 'FETCH_SUCCESS':
|
|
13
|
-
return { ...state, loading: false, token: action.token };
|
|
14
|
+
return { ...state, loading: false, token: action.token, error: null };
|
|
14
15
|
case 'FETCH_ERROR':
|
|
15
16
|
return { ...state, loading: false, error: action.error };
|
|
16
17
|
case 'RESET':
|
|
@@ -24,18 +25,18 @@ function getRefreshDelay(timeUntilExpiry) {
|
|
|
24
25
|
const idealDelay = (timeUntilExpiry - TOKEN_EXPIRY_BUFFER_SECONDS) * 1000;
|
|
25
26
|
return Math.min(Math.max(idealDelay, MIN_REFRESH_DELAY_SECONDS * 1000), MAX_REFRESH_DELAY_SECONDS * 1000);
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
+
function parseTokenPayload(token) {
|
|
28
29
|
// istanbul ignore next
|
|
29
30
|
if (!token) {
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
32
33
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
34
|
+
const { payload } = decodeJwt(token);
|
|
35
|
+
const now = Math.floor(Date.now() / 1000);
|
|
36
|
+
// istanbul ignore next - if the token does not have an exp claim, we cannot determine expiry
|
|
37
|
+
if (typeof payload.exp !== 'number') {
|
|
35
38
|
return null;
|
|
36
39
|
}
|
|
37
|
-
const payload = JSON.parse(atob(parts[1]));
|
|
38
|
-
const now = Math.floor(Date.now() / 1000);
|
|
39
40
|
return {
|
|
40
41
|
payload,
|
|
41
42
|
expiresAt: payload.exp,
|
|
@@ -67,40 +68,57 @@ export function useAccessToken() {
|
|
|
67
68
|
refreshTimeoutRef.current = undefined;
|
|
68
69
|
}
|
|
69
70
|
}, []);
|
|
71
|
+
// Store the current token in a ref to avoid stale closures
|
|
72
|
+
const currentTokenRef = useRef(state.token);
|
|
73
|
+
currentTokenRef.current = state.token;
|
|
74
|
+
// Store updateToken in a ref to break circular dependency
|
|
75
|
+
const updateTokenRef = useRef();
|
|
76
|
+
// Centralized timer scheduling function
|
|
77
|
+
const scheduleNextRefresh = useCallback((delay) => {
|
|
78
|
+
clearRefreshTimeout();
|
|
79
|
+
refreshTimeoutRef.current = setTimeout(() => {
|
|
80
|
+
if (updateTokenRef.current) {
|
|
81
|
+
updateTokenRef.current();
|
|
82
|
+
}
|
|
83
|
+
}, delay);
|
|
84
|
+
}, [clearRefreshTimeout]);
|
|
70
85
|
const updateToken = useCallback(async () => {
|
|
71
86
|
// istanbul ignore next - safety guard against concurrent fetches
|
|
72
87
|
if (fetchingRef.current) {
|
|
73
88
|
return;
|
|
74
89
|
}
|
|
75
90
|
fetchingRef.current = true;
|
|
76
|
-
dispatch({ type: 'FETCH_START' });
|
|
77
91
|
try {
|
|
78
92
|
let token = await getAccessTokenAction();
|
|
79
93
|
if (token) {
|
|
80
|
-
const tokenData =
|
|
94
|
+
const tokenData = parseTokenPayload(token);
|
|
81
95
|
if (!tokenData || tokenData.isExpiring) {
|
|
82
96
|
token = await refreshAccessTokenAction();
|
|
83
97
|
}
|
|
84
98
|
}
|
|
85
|
-
|
|
99
|
+
// Only update state if token has changed
|
|
100
|
+
if (token !== currentTokenRef.current) {
|
|
101
|
+
dispatch({ type: 'FETCH_SUCCESS', token });
|
|
102
|
+
}
|
|
86
103
|
if (token) {
|
|
87
|
-
const tokenData =
|
|
104
|
+
const tokenData = parseTokenPayload(token);
|
|
88
105
|
if (tokenData) {
|
|
89
106
|
const delay = getRefreshDelay(tokenData.timeUntilExpiry);
|
|
90
|
-
|
|
91
|
-
refreshTimeoutRef.current = setTimeout(updateToken, delay);
|
|
107
|
+
scheduleNextRefresh(delay);
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
return token;
|
|
95
111
|
}
|
|
96
112
|
catch (error) {
|
|
97
113
|
dispatch({ type: 'FETCH_ERROR', error: error instanceof Error ? error : new Error(String(error)) });
|
|
98
|
-
|
|
114
|
+
scheduleNextRefresh(RETRY_DELAY_SECONDS * 1000);
|
|
99
115
|
}
|
|
100
116
|
finally {
|
|
101
117
|
fetchingRef.current = false;
|
|
102
118
|
}
|
|
103
|
-
}, [
|
|
119
|
+
}, [scheduleNextRefresh]);
|
|
120
|
+
// Assign updateToken to ref for use in scheduleNextRefresh
|
|
121
|
+
updateTokenRef.current = updateToken;
|
|
104
122
|
const refresh = useCallback(async () => {
|
|
105
123
|
if (fetchingRef.current) {
|
|
106
124
|
return;
|
|
@@ -112,11 +130,10 @@ export function useAccessToken() {
|
|
|
112
130
|
const token = await getAccessTokenAction();
|
|
113
131
|
dispatch({ type: 'FETCH_SUCCESS', token });
|
|
114
132
|
if (token) {
|
|
115
|
-
const tokenData =
|
|
133
|
+
const tokenData = parseTokenPayload(token);
|
|
116
134
|
if (tokenData) {
|
|
117
135
|
const delay = getRefreshDelay(tokenData.timeUntilExpiry);
|
|
118
|
-
|
|
119
|
-
refreshTimeoutRef.current = setTimeout(updateToken, delay);
|
|
136
|
+
scheduleNextRefresh(delay);
|
|
120
137
|
}
|
|
121
138
|
}
|
|
122
139
|
return token;
|
|
@@ -124,12 +141,12 @@ export function useAccessToken() {
|
|
|
124
141
|
catch (error) {
|
|
125
142
|
const typedError = error instanceof Error ? error : new Error(String(error));
|
|
126
143
|
dispatch({ type: 'FETCH_ERROR', error: typedError });
|
|
127
|
-
|
|
144
|
+
scheduleNextRefresh(RETRY_DELAY_SECONDS * 1000);
|
|
128
145
|
}
|
|
129
146
|
finally {
|
|
130
147
|
fetchingRef.current = false;
|
|
131
148
|
}
|
|
132
|
-
}, [refreshAuth,
|
|
149
|
+
}, [refreshAuth, scheduleNextRefresh, updateToken]);
|
|
133
150
|
useEffect(() => {
|
|
134
151
|
if (!user) {
|
|
135
152
|
dispatch({ type: 'RESET' });
|
|
@@ -138,7 +155,7 @@ export function useAccessToken() {
|
|
|
138
155
|
}
|
|
139
156
|
updateToken();
|
|
140
157
|
return clearRefreshTimeout;
|
|
141
|
-
}, [userId, sessionId,
|
|
158
|
+
}, [userId, sessionId, clearRefreshTimeout]);
|
|
142
159
|
return {
|
|
143
160
|
accessToken: state.token,
|
|
144
161
|
loading: state.loading,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAccessToken.js","sourceRoot":"","sources":["../../../src/components/useAccessToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"useAccessToken.js","sourceRoot":"","sources":["../../../src/components/useAccessToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,MAAM,yBAAyB,GAAG,EAAE,CAAC,CAAC,wCAAwC;AAC9E,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,WAAW;AAC3D,MAAM,mBAAmB,GAAG,GAAG,CAAC,CAAC,YAAY;AAc7C,SAAS,YAAY,CAAC,KAAiB,EAAE,MAAmB;IAC1D,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,aAAa;YAChB,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAClD,KAAK,eAAe;YAClB,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACxE,KAAK,aAAa;YAChB,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3D,KAAK,OAAO;YACV,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACrE,uBAAuB;QACvB;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,eAAuB;IAC9C,MAAM,UAAU,GAAG,CAAC,eAAe,GAAG,2BAA2B,CAAC,GAAG,IAAI,CAAC;IAC1E,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC,CAAC;AAC5G,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAyB;IAClD,uBAAuB;IACvB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAE1C,6FAA6F;QAC7F,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO;YACL,OAAO;YACP,SAAS,EAAE,OAAO,CAAC,GAAG;YACtB,UAAU,EAAE,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,2BAA2B;YAC3D,eAAe,EAAE,OAAO,CAAC,GAAG,GAAG,GAAG;SACnC,CAAC;IACJ,CAAC;IAAC,WAAM,CAAC;QACP,uBAAuB;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,OAAO,EAAE,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,EAAE,CAAC;IACxB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,YAAY,EAAE;QACjD,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,IAAI;KACZ,CAAC,CAAC;IAEH,MAAM,iBAAiB,GAAG,MAAM,EAAiC,CAAC;IAClE,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAElC,MAAM,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC3C,IAAI,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAC9B,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACxC,iBAAiB,CAAC,OAAO,GAAG,SAAS,CAAC;QACxC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,2DAA2D;IAC3D,MAAM,eAAe,GAAG,MAAM,CAAqB,KAAK,CAAC,KAAK,CAAC,CAAC;IAChE,eAAe,CAAC,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC;IAEtC,0DAA0D;IAC1D,MAAM,cAAc,GAAG,MAAM,EAAqC,CAAC;IAEnE,wCAAwC;IACxC,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,KAAa,EAAE,EAAE;QAChB,mBAAmB,EAAE,CAAC;QACtB,iBAAiB,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1C,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;gBAC3B,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC,EACD,CAAC,mBAAmB,CAAC,CACtB,CAAC;IAEF,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACzC,iEAAiE;QACjE,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAE3B,IAAI,CAAC;YACH,IAAI,KAAK,GAAG,MAAM,oBAAoB,EAAE,CAAC;YACzC,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;oBACvC,KAAK,GAAG,MAAM,wBAAwB,EAAE,CAAC;gBAC3C,CAAC;YACH,CAAC;YAED,yCAAyC;YACzC,IAAI,KAAK,KAAK,eAAe,CAAC,OAAO,EAAE,CAAC;gBACtC,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;YAC7C,CAAC;YAED,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;oBACzD,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;YACpG,mBAAmB,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;QAClD,CAAC;gBAAS,CAAC;YACT,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC;QAC9B,CAAC;IACH,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAE1B,2DAA2D;IAC3D,cAAc,CAAC,OAAO,GAAG,WAAW,CAAC;IAErC,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QACrC,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QAED,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,QAAQ,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;QAElC,IAAI,CAAC;YACH,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,MAAM,oBAAoB,EAAE,CAAC;YAE3C,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;YAE3C,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;gBAC3C,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;oBACzD,mBAAmB,CAAC,KAAK,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7E,QAAQ,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;YACrD,mBAAmB,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;QAClD,CAAC;gBAAS,CAAC;YACT,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC;QAC9B,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAAC,CAAC;IAEpD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YAC5B,mBAAmB,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QACD,WAAW,EAAE,CAAC;QAEd,OAAO,mBAAmB,CAAC;IAC7B,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAE7C,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,KAAK;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useAccessToken } from './useAccessToken.js';
|
|
3
|
-
import { decodeJwt } from '
|
|
3
|
+
import { decodeJwt } from '../jwt.js';
|
|
4
4
|
/**
|
|
5
5
|
* A hook that retrieves the claims from the access token.
|
|
6
6
|
*
|
|
@@ -17,7 +17,7 @@ export function useTokenClaims() {
|
|
|
17
17
|
return {};
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
return decodeJwt(accessToken);
|
|
20
|
+
return decodeJwt(accessToken).payload;
|
|
21
21
|
}
|
|
22
22
|
catch (_a) {
|
|
23
23
|
return {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTokenClaims.js","sourceRoot":"","sources":["../../../src/components/useTokenClaims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"useTokenClaims.js","sourceRoot":"","sources":["../../../src/components/useTokenClaims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAoB,MAAM,WAAW,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE,CAAC;IAEzC,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACH,OAAO,SAAS,CAAI,WAAW,CAAC,CAAC,OAAO,CAAC;QAC3C,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AACpB,CAAC"}
|
package/dist/esm/jwt.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decodes a base64url encoded string
|
|
3
|
+
* @param input The base64url string to decode
|
|
4
|
+
* @returns The decoded string
|
|
5
|
+
*/
|
|
6
|
+
function decodeBase64Url(input) {
|
|
7
|
+
const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
8
|
+
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
|
|
9
|
+
return atob(base64 + padding);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Decodes a JWT token and returns its header and payload
|
|
13
|
+
* @param token The JWT token to decode
|
|
14
|
+
* @return An object containing the decoded header and payload
|
|
15
|
+
* @throws Error if the token is not in a valid JWT format or if decoding fails
|
|
16
|
+
*/
|
|
17
|
+
// should replace this with jose if we ever need to verify the JWT
|
|
18
|
+
export function decodeJwt(token) {
|
|
19
|
+
const parts = token.split('.');
|
|
20
|
+
if (parts.length !== 3) {
|
|
21
|
+
throw new Error('Invalid JWT format');
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const header = JSON.parse(decodeBase64Url(parts[0]));
|
|
25
|
+
const payload = JSON.parse(decodeBase64Url(parts[1]));
|
|
26
|
+
return { header, payload };
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw new Error(`Failed to decode JWT: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/jwt.ts"],"names":[],"mappings":"AAmEA;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAa;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;AAChC,CAAC;AAED;;;;;GAKG;AACH,kEAAkE;AAClE,MAAM,UAAU,SAAS,CACvB,KAAa;IAKb,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAc,CAAC;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAmB,CAAC;QAExE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrG,CAAC;AACH,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
type TokenClaims<T> = Partial<JWTPayload & T>;
|
|
1
|
+
import { type TokenClaims } from '../jwt.js';
|
|
3
2
|
/**
|
|
4
3
|
* A hook that retrieves the claims from the access token.
|
|
5
4
|
*
|
|
@@ -10,4 +9,3 @@ type TokenClaims<T> = Partial<JWTPayload & T>;
|
|
|
10
9
|
* @returns The claims from the access token, or an empty object if the token is not available or cannot be parsed.
|
|
11
10
|
*/
|
|
12
11
|
export declare function useTokenClaims<T = Record<string, unknown>>(): TokenClaims<T>;
|
|
13
|
-
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT (JSON Web Token) Interface Definitions
|
|
3
|
+
*/
|
|
4
|
+
export interface JWTHeader {
|
|
5
|
+
'alg': string;
|
|
6
|
+
'typ'?: string | undefined;
|
|
7
|
+
'cty'?: string | undefined;
|
|
8
|
+
'crit'?: Array<string | Exclude<keyof JWTHeader, 'crit'>> | undefined;
|
|
9
|
+
'kid'?: string | undefined;
|
|
10
|
+
'jku'?: string | undefined;
|
|
11
|
+
'x5u'?: string | string[] | undefined;
|
|
12
|
+
'x5t#S256'?: string | undefined;
|
|
13
|
+
'x5t'?: string | undefined;
|
|
14
|
+
'x5c'?: string | string[] | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* JWT Payload Interface
|
|
18
|
+
*/
|
|
19
|
+
export interface JWTPayload {
|
|
20
|
+
/**
|
|
21
|
+
* Session ID of the JWT, used to identify the session
|
|
22
|
+
*/
|
|
23
|
+
sid: string;
|
|
24
|
+
/**
|
|
25
|
+
* Issuer of the JWT
|
|
26
|
+
*/
|
|
27
|
+
iss: string;
|
|
28
|
+
/**
|
|
29
|
+
* Subject of the JWT
|
|
30
|
+
*/
|
|
31
|
+
sub: string;
|
|
32
|
+
/**
|
|
33
|
+
* Audience of the JWT, can be a single string or an array of strings
|
|
34
|
+
*/
|
|
35
|
+
aud?: string | string[];
|
|
36
|
+
/**
|
|
37
|
+
* Expiration time of the JWT, represented as a Unix timestamp
|
|
38
|
+
*/
|
|
39
|
+
exp: number;
|
|
40
|
+
/**
|
|
41
|
+
* Issued at time of the JWT, represented as a Unix timestamp
|
|
42
|
+
*/
|
|
43
|
+
iat: number;
|
|
44
|
+
/**
|
|
45
|
+
* JWT ID, a unique identifier for the JWT
|
|
46
|
+
*/
|
|
47
|
+
jti: string;
|
|
48
|
+
/**
|
|
49
|
+
* Organization ID associated with the JWT
|
|
50
|
+
*/
|
|
51
|
+
org_id?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Role of the user associated with the JWT
|
|
54
|
+
*/
|
|
55
|
+
role?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Permissions granted to the user associated with the JWT
|
|
58
|
+
*/
|
|
59
|
+
permissions?: string[];
|
|
60
|
+
}
|
|
61
|
+
export type TokenClaims<T> = Partial<JWTPayload & T>;
|
|
62
|
+
/**
|
|
63
|
+
* Decodes a JWT token and returns its header and payload
|
|
64
|
+
* @param token The JWT token to decode
|
|
65
|
+
* @return An object containing the decoded header and payload
|
|
66
|
+
* @throws Error if the token is not in a valid JWT format or if decoding fails
|
|
67
|
+
*/
|
|
68
|
+
export declare function decodeJwt<T = Record<string, unknown>>(token: string): {
|
|
69
|
+
header: JWTHeader;
|
|
70
|
+
payload: TokenClaims<T>;
|
|
71
|
+
};
|
package/dist/esm/workos.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WorkOS } from '@workos-inc/node';
|
|
2
2
|
import { WORKOS_API_HOSTNAME, WORKOS_API_KEY, WORKOS_API_HTTPS, WORKOS_API_PORT } from './env-variables.js';
|
|
3
3
|
import { lazy } from './utils.js';
|
|
4
|
-
export const VERSION = '2.4.
|
|
4
|
+
export const VERSION = '2.4.4';
|
|
5
5
|
const options = {
|
|
6
6
|
apiHostname: WORKOS_API_HOSTNAME,
|
|
7
7
|
https: WORKOS_API_HTTPS ? WORKOS_API_HTTPS === 'true' : true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
|
|
3
|
+
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useState } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
checkSessionAction,
|
|
6
6
|
getAuthAction,
|
|
@@ -52,7 +52,7 @@ export const AuthKitProvider = ({ children, onSessionExpired }: AuthKitProviderP
|
|
|
52
52
|
const [impersonator, setImpersonator] = useState<Impersonator | undefined>(undefined);
|
|
53
53
|
const [loading, setLoading] = useState(true);
|
|
54
54
|
|
|
55
|
-
const getAuth = async ({ ensureSignedIn = false }: { ensureSignedIn?: boolean } = {}) => {
|
|
55
|
+
const getAuth = useCallback(async ({ ensureSignedIn = false }: { ensureSignedIn?: boolean } = {}) => {
|
|
56
56
|
setLoading(true);
|
|
57
57
|
try {
|
|
58
58
|
const auth = await getAuthAction({ ensureSignedIn });
|
|
@@ -76,48 +76,51 @@ export const AuthKitProvider = ({ children, onSessionExpired }: AuthKitProviderP
|
|
|
76
76
|
} finally {
|
|
77
77
|
setLoading(false);
|
|
78
78
|
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const switchToOrganization =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return result;
|
|
93
|
-
};
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const switchToOrganization = useCallback(
|
|
82
|
+
async (organizationId: string, options: SwitchToOrganizationOptions = {}) => {
|
|
83
|
+
const opts = { revalidationStrategy: 'none', ...options };
|
|
84
|
+
const result = await switchToOrganizationAction(organizationId, {
|
|
85
|
+
revalidationStrategy: 'none',
|
|
86
|
+
...options,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (opts.revalidationStrategy === 'none') {
|
|
90
|
+
await getAuth({ ensureSignedIn: true });
|
|
91
|
+
}
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
setLoading(true);
|
|
101
|
-
const auth = await refreshAuthAction({ ensureSignedIn, organizationId });
|
|
93
|
+
return result;
|
|
94
|
+
},
|
|
95
|
+
[],
|
|
96
|
+
);
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
98
|
+
const refreshAuth = useCallback(
|
|
99
|
+
async ({ ensureSignedIn = false, organizationId }: { ensureSignedIn?: boolean; organizationId?: string } = {}) => {
|
|
100
|
+
try {
|
|
101
|
+
setLoading(true);
|
|
102
|
+
const auth = await refreshAuthAction({ ensureSignedIn, organizationId });
|
|
103
|
+
|
|
104
|
+
setUser(auth.user);
|
|
105
|
+
setSessionId(auth.sessionId);
|
|
106
|
+
setOrganizationId(auth.organizationId);
|
|
107
|
+
setRole(auth.role);
|
|
108
|
+
setPermissions(auth.permissions);
|
|
109
|
+
setEntitlements(auth.entitlements);
|
|
110
|
+
setFeatureFlags(auth.featureFlags);
|
|
111
|
+
setImpersonator(auth.impersonator);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return error instanceof Error ? { error: error.message } : { error: String(error) };
|
|
114
|
+
} finally {
|
|
115
|
+
setLoading(false);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
[],
|
|
119
|
+
);
|
|
117
120
|
|
|
118
|
-
const signOut = async ({ returnTo }: { returnTo?: string } = {}) => {
|
|
121
|
+
const signOut = useCallback(async ({ returnTo }: { returnTo?: string } = {}) => {
|
|
119
122
|
await handleSignOutAction({ returnTo });
|
|
120
|
-
};
|
|
123
|
+
}, []);
|
|
121
124
|
|
|
122
125
|
useEffect(() => {
|
|
123
126
|
getAuth();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useReducer, useRef } from 'react';
|
|
2
2
|
import { getAccessTokenAction, refreshAccessTokenAction } from '../actions.js';
|
|
3
3
|
import { useAuth } from './authkit-provider.js';
|
|
4
|
+
import { decodeJwt } from '../jwt.js';
|
|
4
5
|
|
|
5
6
|
const TOKEN_EXPIRY_BUFFER_SECONDS = 60;
|
|
6
7
|
const MIN_REFRESH_DELAY_SECONDS = 15; // minimum delay before refreshing token
|
|
@@ -24,7 +25,7 @@ function tokenReducer(state: TokenState, action: TokenAction): TokenState {
|
|
|
24
25
|
case 'FETCH_START':
|
|
25
26
|
return { ...state, loading: true, error: null };
|
|
26
27
|
case 'FETCH_SUCCESS':
|
|
27
|
-
return { ...state, loading: false, token: action.token };
|
|
28
|
+
return { ...state, loading: false, token: action.token, error: null };
|
|
28
29
|
case 'FETCH_ERROR':
|
|
29
30
|
return { ...state, loading: false, error: action.error };
|
|
30
31
|
case 'RESET':
|
|
@@ -40,21 +41,21 @@ function getRefreshDelay(timeUntilExpiry: number) {
|
|
|
40
41
|
return Math.min(Math.max(idealDelay, MIN_REFRESH_DELAY_SECONDS * 1000), MAX_REFRESH_DELAY_SECONDS * 1000);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function
|
|
44
|
+
function parseTokenPayload(token: string | undefined) {
|
|
44
45
|
// istanbul ignore next
|
|
45
46
|
if (!token) {
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
try {
|
|
50
|
-
const
|
|
51
|
-
|
|
51
|
+
const { payload } = decodeJwt(token);
|
|
52
|
+
const now = Math.floor(Date.now() / 1000);
|
|
53
|
+
|
|
54
|
+
// istanbul ignore next - if the token does not have an exp claim, we cannot determine expiry
|
|
55
|
+
if (typeof payload.exp !== 'number') {
|
|
52
56
|
return null;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
const payload = JSON.parse(atob(parts[1]));
|
|
56
|
-
const now = Math.floor(Date.now() / 1000);
|
|
57
|
-
|
|
58
59
|
return {
|
|
59
60
|
payload,
|
|
60
61
|
expiresAt: payload.exp,
|
|
@@ -89,6 +90,26 @@ export function useAccessToken() {
|
|
|
89
90
|
}
|
|
90
91
|
}, []);
|
|
91
92
|
|
|
93
|
+
// Store the current token in a ref to avoid stale closures
|
|
94
|
+
const currentTokenRef = useRef<string | undefined>(state.token);
|
|
95
|
+
currentTokenRef.current = state.token;
|
|
96
|
+
|
|
97
|
+
// Store updateToken in a ref to break circular dependency
|
|
98
|
+
const updateTokenRef = useRef<() => Promise<string | undefined>>();
|
|
99
|
+
|
|
100
|
+
// Centralized timer scheduling function
|
|
101
|
+
const scheduleNextRefresh = useCallback(
|
|
102
|
+
(delay: number) => {
|
|
103
|
+
clearRefreshTimeout();
|
|
104
|
+
refreshTimeoutRef.current = setTimeout(() => {
|
|
105
|
+
if (updateTokenRef.current) {
|
|
106
|
+
updateTokenRef.current();
|
|
107
|
+
}
|
|
108
|
+
}, delay);
|
|
109
|
+
},
|
|
110
|
+
[clearRefreshTimeout],
|
|
111
|
+
);
|
|
112
|
+
|
|
92
113
|
const updateToken = useCallback(async () => {
|
|
93
114
|
// istanbul ignore next - safety guard against concurrent fetches
|
|
94
115
|
if (fetchingRef.current) {
|
|
@@ -96,35 +117,40 @@ export function useAccessToken() {
|
|
|
96
117
|
}
|
|
97
118
|
|
|
98
119
|
fetchingRef.current = true;
|
|
99
|
-
|
|
120
|
+
|
|
100
121
|
try {
|
|
101
122
|
let token = await getAccessTokenAction();
|
|
102
123
|
if (token) {
|
|
103
|
-
const tokenData =
|
|
124
|
+
const tokenData = parseTokenPayload(token);
|
|
104
125
|
if (!tokenData || tokenData.isExpiring) {
|
|
105
126
|
token = await refreshAccessTokenAction();
|
|
106
127
|
}
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
|
|
130
|
+
// Only update state if token has changed
|
|
131
|
+
if (token !== currentTokenRef.current) {
|
|
132
|
+
dispatch({ type: 'FETCH_SUCCESS', token });
|
|
133
|
+
}
|
|
110
134
|
|
|
111
135
|
if (token) {
|
|
112
|
-
const tokenData =
|
|
136
|
+
const tokenData = parseTokenPayload(token);
|
|
113
137
|
if (tokenData) {
|
|
114
138
|
const delay = getRefreshDelay(tokenData.timeUntilExpiry);
|
|
115
|
-
|
|
116
|
-
refreshTimeoutRef.current = setTimeout(updateToken, delay);
|
|
139
|
+
scheduleNextRefresh(delay);
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
return token;
|
|
121
144
|
} catch (error) {
|
|
122
145
|
dispatch({ type: 'FETCH_ERROR', error: error instanceof Error ? error : new Error(String(error)) });
|
|
123
|
-
|
|
146
|
+
scheduleNextRefresh(RETRY_DELAY_SECONDS * 1000);
|
|
124
147
|
} finally {
|
|
125
148
|
fetchingRef.current = false;
|
|
126
149
|
}
|
|
127
|
-
}, [
|
|
150
|
+
}, [scheduleNextRefresh]);
|
|
151
|
+
|
|
152
|
+
// Assign updateToken to ref for use in scheduleNextRefresh
|
|
153
|
+
updateTokenRef.current = updateToken;
|
|
128
154
|
|
|
129
155
|
const refresh = useCallback(async () => {
|
|
130
156
|
if (fetchingRef.current) {
|
|
@@ -141,11 +167,10 @@ export function useAccessToken() {
|
|
|
141
167
|
dispatch({ type: 'FETCH_SUCCESS', token });
|
|
142
168
|
|
|
143
169
|
if (token) {
|
|
144
|
-
const tokenData =
|
|
170
|
+
const tokenData = parseTokenPayload(token);
|
|
145
171
|
if (tokenData) {
|
|
146
172
|
const delay = getRefreshDelay(tokenData.timeUntilExpiry);
|
|
147
|
-
|
|
148
|
-
refreshTimeoutRef.current = setTimeout(updateToken, delay);
|
|
173
|
+
scheduleNextRefresh(delay);
|
|
149
174
|
}
|
|
150
175
|
}
|
|
151
176
|
|
|
@@ -153,11 +178,11 @@ export function useAccessToken() {
|
|
|
153
178
|
} catch (error) {
|
|
154
179
|
const typedError = error instanceof Error ? error : new Error(String(error));
|
|
155
180
|
dispatch({ type: 'FETCH_ERROR', error: typedError });
|
|
156
|
-
|
|
181
|
+
scheduleNextRefresh(RETRY_DELAY_SECONDS * 1000);
|
|
157
182
|
} finally {
|
|
158
183
|
fetchingRef.current = false;
|
|
159
184
|
}
|
|
160
|
-
}, [refreshAuth,
|
|
185
|
+
}, [refreshAuth, scheduleNextRefresh, updateToken]);
|
|
161
186
|
|
|
162
187
|
useEffect(() => {
|
|
163
188
|
if (!user) {
|
|
@@ -168,7 +193,7 @@ export function useAccessToken() {
|
|
|
168
193
|
updateToken();
|
|
169
194
|
|
|
170
195
|
return clearRefreshTimeout;
|
|
171
|
-
}, [userId, sessionId,
|
|
196
|
+
}, [userId, sessionId, clearRefreshTimeout]);
|
|
172
197
|
|
|
173
198
|
return {
|
|
174
199
|
accessToken: state.token,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useAccessToken } from './useAccessToken.js';
|
|
3
|
-
import { decodeJwt, type
|
|
4
|
-
|
|
5
|
-
type TokenClaims<T> = Partial<JWTPayload & T>;
|
|
3
|
+
import { decodeJwt, type TokenClaims } from '../jwt.js';
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
6
|
* A hook that retrieves the claims from the access token.
|
|
@@ -22,7 +20,7 @@ export function useTokenClaims<T = Record<string, unknown>>(): TokenClaims<T> {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
try {
|
|
25
|
-
return decodeJwt<T>(accessToken);
|
|
23
|
+
return decodeJwt<T>(accessToken).payload;
|
|
26
24
|
} catch {
|
|
27
25
|
return {};
|
|
28
26
|
}
|
package/src/jwt.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT (JSON Web Token) Interface Definitions
|
|
3
|
+
*/
|
|
4
|
+
export interface JWTHeader {
|
|
5
|
+
'alg': string;
|
|
6
|
+
'typ'?: string | undefined;
|
|
7
|
+
'cty'?: string | undefined;
|
|
8
|
+
'crit'?: Array<string | Exclude<keyof JWTHeader, 'crit'>> | undefined;
|
|
9
|
+
'kid'?: string | undefined;
|
|
10
|
+
'jku'?: string | undefined;
|
|
11
|
+
'x5u'?: string | string[] | undefined;
|
|
12
|
+
'x5t#S256'?: string | undefined;
|
|
13
|
+
'x5t'?: string | undefined;
|
|
14
|
+
'x5c'?: string | string[] | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* JWT Payload Interface
|
|
18
|
+
*/
|
|
19
|
+
export interface JWTPayload {
|
|
20
|
+
/**
|
|
21
|
+
* Session ID of the JWT, used to identify the session
|
|
22
|
+
*/
|
|
23
|
+
sid: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Issuer of the JWT
|
|
27
|
+
*/
|
|
28
|
+
iss: string;
|
|
29
|
+
/**
|
|
30
|
+
* Subject of the JWT
|
|
31
|
+
*/
|
|
32
|
+
sub: string;
|
|
33
|
+
/**
|
|
34
|
+
* Audience of the JWT, can be a single string or an array of strings
|
|
35
|
+
*/
|
|
36
|
+
aud?: string | string[];
|
|
37
|
+
/**
|
|
38
|
+
* Expiration time of the JWT, represented as a Unix timestamp
|
|
39
|
+
*/
|
|
40
|
+
exp: number;
|
|
41
|
+
/**
|
|
42
|
+
* Issued at time of the JWT, represented as a Unix timestamp
|
|
43
|
+
*/
|
|
44
|
+
iat: number;
|
|
45
|
+
/**
|
|
46
|
+
* JWT ID, a unique identifier for the JWT
|
|
47
|
+
*/
|
|
48
|
+
jti: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Organization ID associated with the JWT
|
|
52
|
+
*/
|
|
53
|
+
org_id?: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Role of the user associated with the JWT
|
|
57
|
+
*/
|
|
58
|
+
role?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Permissions granted to the user associated with the JWT
|
|
62
|
+
*/
|
|
63
|
+
permissions?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type TokenClaims<T> = Partial<JWTPayload & T>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Decodes a base64url encoded string
|
|
70
|
+
* @param input The base64url string to decode
|
|
71
|
+
* @returns The decoded string
|
|
72
|
+
*/
|
|
73
|
+
function decodeBase64Url(input: string): string {
|
|
74
|
+
const base64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
|
75
|
+
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
|
|
76
|
+
return atob(base64 + padding);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Decodes a JWT token and returns its header and payload
|
|
81
|
+
* @param token The JWT token to decode
|
|
82
|
+
* @return An object containing the decoded header and payload
|
|
83
|
+
* @throws Error if the token is not in a valid JWT format or if decoding fails
|
|
84
|
+
*/
|
|
85
|
+
// should replace this with jose if we ever need to verify the JWT
|
|
86
|
+
export function decodeJwt<T = Record<string, unknown>>(
|
|
87
|
+
token: string,
|
|
88
|
+
): {
|
|
89
|
+
header: JWTHeader;
|
|
90
|
+
payload: TokenClaims<T>;
|
|
91
|
+
} {
|
|
92
|
+
const parts = token.split('.');
|
|
93
|
+
|
|
94
|
+
if (parts.length !== 3) {
|
|
95
|
+
throw new Error('Invalid JWT format');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const header = JSON.parse(decodeBase64Url(parts[0])) as JWTHeader;
|
|
100
|
+
const payload = JSON.parse(decodeBase64Url(parts[1])) as JWTPayload & T;
|
|
101
|
+
|
|
102
|
+
return { header, payload };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new Error(`Failed to decode JWT: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/workos.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { WorkOS } from '@workos-inc/node';
|
|
|
2
2
|
import { WORKOS_API_HOSTNAME, WORKOS_API_KEY, WORKOS_API_HTTPS, WORKOS_API_PORT } from './env-variables.js';
|
|
3
3
|
import { lazy } from './utils.js';
|
|
4
4
|
|
|
5
|
-
export const VERSION = '2.4.
|
|
5
|
+
export const VERSION = '2.4.4';
|
|
6
6
|
|
|
7
7
|
const options = {
|
|
8
8
|
apiHostname: WORKOS_API_HOSTNAME,
|