@@ -11,40 +11,83 @@ const REFRESH_TOKEN_HEADER_NAME = "st-refresh-token";
11
11
const ANTI_CSRF_TOKEN_COOKIE_NAME = "sAntiCsrf" ;
12
12
const ANTI_CSRF_TOKEN_HEADER_NAME = "anti-csrf" ;
13
13
14
+ const REDIRECT_ATTEMPT_MAX_COUNT = 5 ;
15
+ const REDIRECT_PATH_PARAM_NAME = "stRedirectTo" ;
16
+ const REDIRECT_ATTEMPT_COUNT_COOKIE_NAME = "sSsrSessionRefreshAttempt" ;
17
+
14
18
let AppInfo : SuperTokensNextjsConfig [ "appInfo" ] ;
15
19
16
- // export function superTokensMiddleware (config: SuperTokensNextjsConfig, request: NextRequest, response: NextResponse) {
20
+ // export async function superTokensMiddeware (config: SuperTokensNextjsConfig): () => Promise<Response | void> {
17
21
// AppInfo = config.appInfo;
18
22
// if (config.enableDebugLogs) {
19
23
// enableLogging();
20
24
// }
21
- // const url = new URL(request.url);
22
- // if (url.pathname === "/auth/session/refresh") {
23
- // return refreshSession(request, response);
24
- // }
25
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
- // });
26
+ // return (request: Request) => {
27
+ // const url = new URL(request.url);
28
+ // if (url.pathname.startsWith("/api")) {
29
+ // if (request.headers.has("x-user-id")) {
30
+ // console.warn(
31
+ // "The FE tried to pass x-user-id, which is only supposed to be a backend internal header. Ignoring."
32
+ // );
33
+ // request.headers.delete("x-user-id");
34
+ // }
35
+ //
36
+ // if (url.pathname.startsWith("/api/auth/session/refresh") && request.method === "GET") {
37
+ // return refreshSession(ssrConfig(), request);
38
+ // }
39
+ //
40
+ // if (url.pathname.startsWith("/api/auth")) {
41
+ // // this hits our pages/api/auth/* endpoints
42
+ // return;
43
+ // }
44
+ //
45
+ // return withSession(request, async (err, session) => {
46
+ // if (err) {
47
+ // return NextResponse.json(err, { status: 500 });
48
+ // }
49
+ //
50
+ // if (session === undefined) {
51
+ // return NextResponse.next();
52
+ // }
53
+ // return NextResponse.next({
54
+ // headers: {
55
+ // "x-user-id": session.getUserId(),
56
+ // },
57
+ // });
58
+ // });
59
+ // }
60
+ // // Save the current path so that we can use it during SSR
61
+ // // Used to redirect the user to the correct path after login/refresh
62
+ // // https://github.com/vercel/next.js/issues/43704#issuecomment-2090798307
63
+ // // TL;DR: You can not access pathname in SSR and requests that originate from redirect()
64
+ // // do not have an origin header
65
+ // const response = NextResponse.next();
66
+ // response.cookies.set("sCurrentPath", request.nextUrl.pathname);
67
+ // };
33
68
// }
34
69
35
70
export async function refreshSession ( config : SuperTokensNextjsConfig , request : Request ) : Promise < Response > {
36
71
AppInfo = config . appInfo ;
37
72
if ( config . enableDebugLogs ) {
38
73
enableLogging ( ) ;
39
74
}
40
- console . log ( request ) ;
75
+
76
+ // Cancel the refresh cycle if an unforseen state is encountered
77
+ const redirectAttemptNumber = getRedirectAttemptNumber ( request ) ;
78
+ if ( redirectAttemptNumber > REDIRECT_ATTEMPT_MAX_COUNT ) {
79
+ return redirectToAuthPage ( request ) ;
80
+ }
81
+
41
82
const refreshToken =
42
83
getCookie ( request , REFRESH_TOKEN_COOKIE_NAME ) || request . headers . get ( REFRESH_TOKEN_HEADER_NAME ) ;
43
84
if ( ! refreshToken ) {
44
85
logDebugMessage ( "Refresh token not found" ) ;
45
86
return redirectToAuthPage ( request ) ;
46
87
}
47
88
89
+ const requestUrl = new URL ( request . url ) ;
90
+ const redirectTo = requestUrl . searchParams . get ( REDIRECT_PATH_PARAM_NAME ) || "/" ;
48
91
try {
49
92
const tokens = await fetchNewTokens ( refreshToken ) ;
50
93
const hasRequiredCookies = tokens . accessToken . cookie && tokens . refreshToken . cookie && tokens . frontToken . cookie ;
@@ -53,35 +96,35 @@ export async function refreshSession(config: SuperTokensNextjsConfig, request: R
53
96
logDebugMessage ( "Missing tokens from refresh response" ) ;
54
97
return redirectToAuthPage ( request ) ;
55
98
}
56
- const currentUrl = new URL ( request . url ) ;
57
- const redirectUrl = new URL ( currentUrl . searchParams . get ( "redirectTo" ) || "/" , request . url ) ;
99
+ const redirectUrl = new URL ( redirectTo , request . url ) ;
58
100
const finalResponse = new Response ( null , {
59
101
status : 307 ,
60
102
headers : {
61
103
Location : redirectUrl . toString ( ) ,
62
- "x-current-path" : redirectUrl . pathname ,
63
- "x-st-ssr-session-refresh-attempt" : "0" ,
64
104
} ,
65
105
} ) ;
106
+ finalResponse . headers . append ( FRONT_TOKEN_HEADER_NAME , tokens . frontToken . header as string ) ;
66
107
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 ) ;
108
+ finalResponse . headers . append ( "set-cookie" , `${ tokens . accessToken . cookie as string } ` ) ;
109
+ finalResponse . headers . append ( "set-cookie" , `${ tokens . refreshToken . cookie as string } ` ) ;
110
+ finalResponse . headers . append ( "set-cookie" , `${ tokens . frontToken . cookie as string } ` ) ;
76
111
}
77
112
if ( hasRequiredHeaders ) {
78
113
finalResponse . headers . append ( REFRESH_TOKEN_HEADER_NAME , tokens . refreshToken . header as string ) ;
79
114
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
115
}
116
+
117
+ if ( tokens . antiCsrfToken . cookie ) {
118
+ finalResponse . headers . append ( "set-cookie" , `${ tokens . antiCsrfToken . cookie as string } ` ) ;
119
+ } else if ( tokens . antiCsrfToken . header ) {
120
+ finalResponse . headers . append ( ANTI_CSRF_TOKEN_HEADER_NAME , tokens . antiCsrfToken . header ) ;
121
+ }
122
+ finalResponse . headers . append (
123
+ "set-cookie" ,
124
+ `${ REDIRECT_ATTEMPT_COUNT_COOKIE_NAME } =${
125
+ redirectAttemptNumber + 1
126
+ } ; Path=/; Max-Age=10; HttpOnly; SameSite=Strict`
127
+ ) ;
85
128
logDebugMessage ( "Attached new tokens to response" ) ;
86
129
return finalResponse ;
87
130
} catch ( err ) {
@@ -162,3 +205,13 @@ function getCookie(request: Request, name: string) {
162
205
. find ( ( row ) => row . startsWith ( `${ name } =` ) )
163
206
?. split ( "=" ) [ 1 ] ;
164
207
}
208
+
209
+ function getRedirectAttemptNumber ( request : Request ) : number {
210
+ const cookieValue = getCookie ( request , REDIRECT_ATTEMPT_COUNT_COOKIE_NAME ) || "1" ;
211
+
212
+ try {
213
+ return parseInt ( cookieValue ) ;
214
+ } catch ( err ) {
215
+ return 1 ;
216
+ }
217
+ }
0 commit comments