1
1
import { enableLogging , logDebugMessage } from "../logger" ;
2
2
3
- import type { NextRequest , NextResponse , SuperTokensNextjsConfig } from "./types" ;
3
+ import type { NextRequest , SuperTokensNextjsConfig , SuperTokensRequestToken } from "./types" ;
4
4
5
5
const ACCESS_TOKEN_COOKIE_NAME = "sAccessToken" ;
6
6
const ACCESS_TOKEN_HEADER_NAME = "st-access-token" ;
@@ -9,14 +9,30 @@ const FRONT_TOKEN_HEADER_NAME = "front-token";
9
9
const REFRESH_TOKEN_COOKIE_NAME = "sRefreshToken" ;
10
10
const REFRESH_TOKEN_HEADER_NAME = "st-refresh-token" ;
11
11
const ANTI_CSRF_TOKEN_COOKIE_NAME = "sAntiCsrf" ;
12
+ const ANTI_CSRF_TOKEN_HEADER_NAME = "anti-csrf" ;
12
13
13
14
let AppInfo : SuperTokensNextjsConfig [ "appInfo" ] ;
14
15
15
- export async function refreshSession (
16
- config : SuperTokensNextjsConfig ,
17
- request : NextRequest ,
18
- response : NextResponse
19
- ) : Promise < NextResponse > {
16
+ // export function superTokensMiddleware(config: SuperTokensNextjsConfig, request: NextRequest, response: NextResponse) {
17
+ // AppInfo = config.appInfo;
18
+ // if (config.enableDebugLogs) {
19
+ // enableLogging();
20
+ // }
21
+ // const url = new URL(request.url);
22
+ // if (url.pathname === "/auth/session/refresh") {
23
+ // return refreshSession(request, response);
24
+ // }
25
+ //
26
+ // // Save the current path so that we can use it during SSR
27
+ // // Used to redirect the user to the correct path after login/refresh
28
+ // return response.next({
29
+ // headers: {
30
+ // "x-current-path": url.pathname,
31
+ // },
32
+ // });
33
+ // }
34
+
35
+ export async function refreshSession ( config : SuperTokensNextjsConfig , request : NextRequest ) : Promise < Response > {
20
36
AppInfo = config . appInfo ;
21
37
if ( config . enableDebugLogs ) {
22
38
enableLogging ( ) ;
@@ -25,43 +41,73 @@ export async function refreshSession(
25
41
request . cookies . get ( REFRESH_TOKEN_COOKIE_NAME ) ?. value || request . headers . get ( REFRESH_TOKEN_HEADER_NAME ) ;
26
42
if ( ! refreshToken ) {
27
43
logDebugMessage ( "Refresh token not found" ) ;
28
- return redirectToAuthPage ( request , response ) ;
44
+ return redirectToAuthPage ( request ) ;
29
45
}
30
46
31
47
try {
32
48
const tokens = await fetchNewTokens ( refreshToken ) ;
33
- if ( ! tokens . accessToken || ! tokens . refreshToken || ! tokens . frontToken ) {
49
+ const hasRequiredCookies = tokens . accessToken . cookie && tokens . refreshToken . cookie && tokens . frontToken . cookie ;
50
+ const hasRequiredHeaders = tokens . accessToken . header && tokens . refreshToken . header && tokens . frontToken . header ;
51
+ if ( ! hasRequiredCookies && ! hasRequiredHeaders ) {
34
52
logDebugMessage ( "Missing tokens from refresh response" ) ;
35
- return redirectToAuthPage ( request , response ) ;
53
+ return redirectToAuthPage ( request ) ;
36
54
}
37
55
38
56
const currentUrl = new URL ( request . url ) ;
39
57
const redirectUrl = new URL ( currentUrl . searchParams . get ( "redirectTo" ) || "/" , request . url ) ;
40
- const finalResponse = response . redirect ( redirectUrl ) ;
41
- finalResponse . headers . set ( "x-current-path" , redirectUrl . pathname ) ;
42
- // @ts -expect-error TS(2345) It complains about tokens being null although we check for that in the previous if condition
43
- attachTokensToResponse ( finalResponse , tokens ) ;
58
+ const finalResponse = new Response ( null , {
59
+ status : 307 ,
60
+ headers : {
61
+ Location : redirectUrl . toString ( ) ,
62
+ "x-current-path" : redirectUrl . pathname ,
63
+ "x-st-ssr-session-refresh-attempt" : "0" ,
64
+ } ,
65
+ } ) ;
66
+ if ( hasRequiredCookies ) {
67
+ finalResponse . headers . append (
68
+ "set-cookie" ,
69
+ `${ tokens . accessToken . cookie as string } ,${ tokens . refreshToken . cookie as string } ,${
70
+ tokens . frontToken . cookie as string
71
+ } `
72
+ ) ;
73
+ finalResponse . headers . append ( "x-middleware-set-cookie" , tokens . accessToken . cookie as string ) ;
74
+ finalResponse . headers . append ( "x-middleware-set-cookie" , tokens . refreshToken . cookie as string ) ;
75
+ finalResponse . headers . append ( "x-middleware-set-cookie" , tokens . frontToken . cookie as string ) ;
76
+ }
77
+ if ( hasRequiredHeaders ) {
78
+ finalResponse . headers . append ( REFRESH_TOKEN_HEADER_NAME , tokens . refreshToken . header as string ) ;
79
+ finalResponse . headers . append ( ACCESS_TOKEN_HEADER_NAME , tokens . accessToken . header as string ) ;
80
+ finalResponse . headers . append ( FRONT_TOKEN_HEADER_NAME , tokens . frontToken . header as string ) ;
81
+ if ( tokens . antiCsrfToken . header ) {
82
+ finalResponse . headers . append ( ANTI_CSRF_TOKEN_HEADER_NAME , tokens . antiCsrfToken . header ) ;
83
+ }
84
+ }
44
85
logDebugMessage ( "Attached new tokens to response" ) ;
45
86
return finalResponse ;
46
87
} catch ( err ) {
47
88
logDebugMessage ( "Error refreshing session" ) ;
48
89
logDebugMessage ( err as unknown as string ) ;
49
- return redirectToAuthPage ( request , response ) ;
90
+ return redirectToAuthPage ( request ) ;
50
91
}
51
92
}
52
93
53
- function redirectToAuthPage ( request : NextRequest , response : NextResponse ) : NextResponse {
94
+ function redirectToAuthPage ( request : NextRequest ) : Response {
54
95
const authPagePath = AppInfo . websiteBasePath || "/auth" ;
55
96
const redirectUrl = new URL ( authPagePath , request . url ) ;
56
97
logDebugMessage ( `Redirecting to: ${ redirectUrl } ` ) ;
57
- return response . redirect ( redirectUrl ) ;
98
+ return new Response ( null , {
99
+ status : 307 ,
100
+ headers : {
101
+ Location : redirectUrl . toString ( ) ,
102
+ } ,
103
+ } ) ;
58
104
}
59
105
60
106
async function fetchNewTokens ( currentRefreshToken : string ) : Promise < {
61
- accessToken : string | null ;
62
- refreshToken : string | null ;
63
- frontToken : string | null ;
64
- antiCsrf : string | null ;
107
+ accessToken : SuperTokensRequestToken ;
108
+ refreshToken : SuperTokensRequestToken ;
109
+ frontToken : SuperTokensRequestToken ;
110
+ antiCsrfToken : SuperTokensRequestToken ;
65
111
} > {
66
112
const refreshApiURL = new URL ( `${ AppInfo . apiBasePath } /session/refresh` , AppInfo . apiDomain ) ;
67
113
const refreshResponse = await fetch ( refreshApiURL , {
@@ -73,55 +119,38 @@ async function fetchNewTokens(currentRefreshToken: string): Promise<{
73
119
credentials : "include" ,
74
120
} ) ;
75
121
logDebugMessage ( "Session refresh request completed" ) ;
76
- const frontToken = refreshResponse . headers . get ( "front-token" ) ;
77
-
78
- let accessToken : string | null = null ;
79
- let refreshToken : string | null = null ;
80
- let antiCsrf : string | null = null ;
122
+ const frontToken : SuperTokensRequestToken = {
123
+ header : refreshResponse . headers . get ( "front-token" ) ,
124
+ cookie : `${ FRONT_TOKEN_COOKIE_NAME } =${ refreshResponse . headers . get ( "front-token" ) } ; Path=/` ,
125
+ } ;
126
+ const accessToken : SuperTokensRequestToken = {
127
+ header : refreshResponse . headers . get ( ACCESS_TOKEN_HEADER_NAME ) ,
128
+ cookie : null ,
129
+ } ;
130
+ const refreshToken : SuperTokensRequestToken = {
131
+ header : refreshResponse . headers . get ( REFRESH_TOKEN_HEADER_NAME ) ,
132
+ cookie : null ,
133
+ } ;
134
+ const antiCsrfToken : SuperTokensRequestToken = {
135
+ header : refreshResponse . headers . get ( ANTI_CSRF_TOKEN_HEADER_NAME ) ,
136
+ cookie : null ,
137
+ } ;
81
138
82
139
// getSetCookie was added in node 18 and our build target is ES5
83
140
// This should not a problem here since the function runs in the Vercel edge runtime environment
84
141
// @ts -expect-error TS(2339): Property 'getSetCookie' does not exist on type 'Headers'.
85
142
const setCookieHeaders = refreshResponse . headers . getSetCookie ( ) ;
86
- if ( ! setCookieHeaders . length ) {
87
- return { accessToken, refreshToken, frontToken, antiCsrf } ;
88
- }
89
-
90
143
for ( const header of setCookieHeaders ) {
91
144
if ( header . includes ( ACCESS_TOKEN_COOKIE_NAME ) ) {
92
- accessToken = getCookieValue ( header , ACCESS_TOKEN_COOKIE_NAME ) ;
145
+ accessToken . cookie = header ;
93
146
}
94
147
if ( header . includes ( REFRESH_TOKEN_COOKIE_NAME ) ) {
95
- refreshToken = getCookieValue ( header , REFRESH_TOKEN_COOKIE_NAME ) ;
148
+ refreshToken . cookie = header ;
96
149
}
97
150
if ( header . includes ( ANTI_CSRF_TOKEN_COOKIE_NAME ) ) {
98
- antiCsrf = getCookieValue ( header , ANTI_CSRF_TOKEN_COOKIE_NAME ) ;
151
+ antiCsrfToken . cookie = header ;
99
152
}
100
153
}
101
154
102
- return { accessToken, refreshToken, frontToken, antiCsrf } ;
103
- }
104
-
105
- function attachTokensToResponse (
106
- response : NextResponse ,
107
- tokens : { accessToken : string ; refreshToken : string ; frontToken : string ; antiCsrf : string | null }
108
- ) {
109
- response . headers . append ( "set-cookie" , `${ ACCESS_TOKEN_COOKIE_NAME } =${ tokens . accessToken } ` ) ;
110
- response . headers . append ( ACCESS_TOKEN_HEADER_NAME , tokens . accessToken ) ;
111
- response . cookies . set ( ACCESS_TOKEN_COOKIE_NAME , tokens . accessToken ) ;
112
- response . headers . append ( "set-cookie" , `${ REFRESH_TOKEN_COOKIE_NAME } =${ tokens . refreshToken } ` ) ;
113
- response . headers . append ( REFRESH_TOKEN_HEADER_NAME , tokens . refreshToken ) ;
114
- response . cookies . set ( REFRESH_TOKEN_COOKIE_NAME , tokens . refreshToken ) ;
115
- response . headers . append ( "set-cookie" , `${ FRONT_TOKEN_COOKIE_NAME } =${ tokens . frontToken } ` ) ;
116
- response . headers . append ( FRONT_TOKEN_HEADER_NAME , tokens . frontToken ) ;
117
- response . cookies . set ( FRONT_TOKEN_COOKIE_NAME , tokens . frontToken ) ;
118
- if ( tokens . antiCsrf ) {
119
- response . headers . append ( "set-cookie" , `${ ANTI_CSRF_TOKEN_COOKIE_NAME } =${ tokens . antiCsrf } ` ) ;
120
- response . cookies . set ( ANTI_CSRF_TOKEN_COOKIE_NAME , tokens . antiCsrf ) ;
121
- }
122
- }
123
-
124
- export function getCookieValue ( header : string , name : string ) : string | null {
125
- const match = header . match ( new RegExp ( `${ name } =([^;]+)` ) ) ;
126
- return match ? match [ 1 ] : null ;
155
+ return { accessToken, refreshToken, frontToken, antiCsrfToken } ;
127
156
}
0 commit comments