@@ -16,6 +16,7 @@ import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose';
1616import { getConfig } from './config.js' ;
1717import { configureSessionStorage , getSessionStorage } from './sessionStorage.js' ;
1818import { isDataWithResponseInit , isJsonResponse , isRedirect , isResponse } from './utils.js' ;
19+ import type { AuthenticationResponse } from '@workos-inc/node' ;
1920
2021// must be a type since this is a subtype of response
2122// interfaces must conform to the types they extend
@@ -37,37 +38,24 @@ export class SessionRefreshError extends Error {
3738 * @param options - Optional configuration options
3839 * @returns A promise that resolves to the new session object
3940 */
40- export async function refreshSession ( request : Request , { organizationId } : { organizationId ?: string } = { } ) {
41- const { getSession, commitSession } = await getSessionStorage ( ) ;
42- const session = await getSessionFromCookie ( request . headers . get ( 'Cookie' ) as string ) ;
43-
41+ export async function refreshSession ( request : Request , options : { organizationId ?: string } = { } ) {
42+ const { organizationId } = options ;
43+ const { getSession } = await getSessionStorage ( ) ;
44+ const cookie = request . headers . get ( 'Cookie' ) ;
45+ const session = cookie ? await getSessionFromCookie ( cookie ) : null ;
4446 if ( ! session ) {
4547 throw redirect ( await getAuthorizationUrl ( ) ) ;
4648 }
4749
4850 try {
49- const { accessToken, refreshToken, user, impersonator } =
50- await getWorkOS ( ) . userManagement . authenticateWithRefreshToken ( {
51- clientId : getConfig ( 'clientId' ) ,
52- refreshToken : session . refreshToken ,
53- organizationId,
54- } ) ;
55-
56- const newSession = {
57- accessToken,
58- refreshToken,
59- user,
60- impersonator,
61- headers : { } as Record < string , string > ,
62- } ;
63-
64- const cookieSession = await getSession ( request . headers . get ( 'Cookie' ) ) ;
65- cookieSession . set ( 'jwt' , await encryptSession ( newSession ) ) ;
66- const cookie = await commitSession ( cookieSession ) ;
67-
68- newSession . headers = {
69- 'Set-Cookie' : cookie ,
70- } ;
51+ const refreshResult = await getWorkOS ( ) . userManagement . authenticateWithRefreshToken ( {
52+ clientId : getConfig ( 'clientId' ) ,
53+ refreshToken : session . refreshToken ,
54+ organizationId,
55+ } ) ;
56+ const { headers } = await saveSession ( refreshResult , request ) ;
57+ const cookieSession = await getSession ( cookie ) ;
58+ const { accessToken, user, impersonator } = refreshResult ;
7159
7260 const {
7361 sessionId,
@@ -91,7 +79,7 @@ export async function refreshSession(request: Request, { organizationId }: { org
9179 featureFlags,
9280 impersonator : impersonator ?? null ,
9381 sealedSession : cookieSession . get ( 'jwt' ) ,
94- headers : newSession . headers ,
82+ headers,
9583 } ;
9684 } catch ( error ) {
9785 throw new Error ( `Failed to refresh session: ${ error instanceof Error ? error . message : String ( error ) } ` , {
@@ -100,7 +88,54 @@ export async function refreshSession(request: Request, { organizationId }: { org
10088 }
10189}
10290
103- async function updateSession ( request : Request , debug : boolean ) {
91+ /**
92+ * Saves a WorkOS session to a cookie for use with AuthKit.
93+ *
94+ * This function is intended for advanced use cases where you need to manually
95+ * manage sessions, such as custom authentication flows (email verification,
96+ * etc.) that don't use the standard AuthKit authentication flow.
97+ *
98+ * @param sessionOrResponse The WorkOS session or AuthenticationResponse
99+ * containing access token, refresh token, and user information.
100+ * @param request A Request object, used to determine cookie settings.
101+ *
102+ * @example
103+ * import { saveSession } from '@workos-inc/authkit-react-router';
104+ *
105+ * async function handleEmailVerification(req: Request) {
106+ * const { code } = await req.json();
107+ * const authResponse = await workos.userManagement.authenticateWithEmailVerification({
108+ * clientId: process.env.WORKOS_CLIENT_ID,
109+ * code,
110+ * });
111+ *
112+ * await saveSession(authResponse, req);
113+ * }
114+ */
115+ export async function saveSession (
116+ sessionOrResponse : Session | AuthenticationResponse ,
117+ request : Request ,
118+ ) : Promise < Session > {
119+ const { getSession, commitSession } = await getSessionStorage ( ) ;
120+ const { accessToken, refreshToken, user, impersonator } = sessionOrResponse ;
121+ const newSession : Session = {
122+ accessToken,
123+ refreshToken,
124+ user,
125+ impersonator,
126+ headers : { } ,
127+ } ;
128+ const cookieSession = await getSession ( request . headers . get ( 'Cookie' ) ) ;
129+ cookieSession . set ( 'jwt' , await encryptSession ( newSession ) ) ;
130+ const cookie = await commitSession ( cookieSession ) ;
131+ newSession . headers = {
132+ 'Set-Cookie' : cookie ,
133+ } ;
134+
135+ return newSession ;
136+ }
137+
138+ async function updateSession ( request : Request , debug : boolean ) : Promise < Session | null > {
104139 const session = await getSessionFromCookie ( request . headers . get ( 'Cookie' ) as string ) ;
105140 const { commitSession, getSession } = await getSessionStorage ( ) ;
106141
@@ -158,7 +193,7 @@ async function updateSession(request: Request, debug: boolean) {
158193 }
159194}
160195
161- export async function encryptSession ( session : Session ) {
196+ export async function encryptSession ( session : Session | AuthenticationResponse ) {
162197 return sealData ( session , {
163198 password : getConfig ( 'cookiePassword' ) ,
164199 ttl : 0 ,
0 commit comments