Skip to content

Commit 4190020

Browse files
committed
Refresh token from middleware
1 parent 08f9ca7 commit 4190020

File tree

5 files changed

+275
-63
lines changed

5 files changed

+275
-63
lines changed

examples/with-next-ssr-app-directory/app/api/auth/[...path]/route.ts

Lines changed: 123 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@ ensureSuperTokensInit();
88

99
const handleCall = getAppDirRequestHandler();
1010

11+
// input
12+
// { refreshSessionWithoutRequestResponse }
13+
// async function
14+
//
15+
1116
export async function GET(request: NextRequest) {
1217
if (request.method === "GET" && request.url.includes("/session/refresh")) {
13-
const searchParams = request.nextUrl.searchParams;
14-
const redirectTo = searchParams.get("redirectTo") || "/";
15-
const cookiesFromReq = await cookies();
16-
const sRefreshToken = cookiesFromReq.get("sRefreshToken")?.value;
17-
const result = await refreshSessionWithoutRequestResponse(sRefreshToken);
18-
const tokens = result.getAllSessionTokensDangerously();
19-
cookiesFromReq.set("sAccessToken", tokens.accessToken);
20-
cookiesFromReq.set("sRefreshToken", tokens.refreshToken);
21-
cookiesFromReq.set("sFrontToken", tokens.frontToken);
22-
return NextResponse.redirect(new URL(redirectTo, request.url));
18+
return refreshSession(request);
2319
}
2420
const res = await handleCall(request);
2521
if (!res.headers.has("Cache-Control")) {
@@ -48,3 +44,120 @@ export async function PATCH(request: NextRequest) {
4844
export async function HEAD(request: NextRequest) {
4945
return handleCall(request);
5046
}
47+
48+
const refreshTokenCookieName = "sRefreshToken";
49+
const refreshTokenHeaderName = "st-refresh-token";
50+
async function refreshSession(request: NextRequest) {
51+
console.log("Attempting session refresh");
52+
53+
const cookiesFromReq = await cookies();
54+
console.log("current cookies", cookiesFromReq);
55+
56+
const refreshToken =
57+
request.cookies.get(refreshTokenCookieName)?.value || request.headers.get(refreshTokenHeaderName);
58+
if (!refreshToken) {
59+
return NextResponse.redirect(new URL("/auth", request.url));
60+
}
61+
62+
const redirectTo = new URL("/", request.url);
63+
64+
try {
65+
const refreshResponse = await fetch(`http://localhost:3000/api/auth/session/refresh`, {
66+
method: "POST",
67+
headers: {
68+
"Content-Type": "application/json",
69+
Cookie: `sRefreshToken=${refreshToken}`,
70+
},
71+
credentials: "include",
72+
});
73+
console.log("Performed session refresh request");
74+
console.log(refreshResponse);
75+
console.log(refreshResponse.headers);
76+
console.log(await refreshResponse.text());
77+
78+
const setCookieHeaders = refreshResponse.headers.getSetCookie();
79+
const frontToken = refreshResponse.headers.get("front-token");
80+
if (!frontToken) {
81+
return NextResponse.redirect(new URL("/auth", request.url));
82+
}
83+
84+
// TODO: Check for csrf token
85+
if (!setCookieHeaders.length) {
86+
return NextResponse.redirect(new URL("/auth", request.url));
87+
}
88+
89+
const response = NextResponse.redirect(redirectTo);
90+
let sAccessToken: string | null = null;
91+
let sRefreshToken: string | null = null;
92+
for (const header of setCookieHeaders) {
93+
if (header.includes("sAccessToken")) {
94+
const match = header.match(/sAccessToken=([^;]+)/);
95+
sAccessToken = match ? match[1] : null;
96+
}
97+
if (header.includes("sRefreshToken")) {
98+
const match = header.match(/sRefreshToken=([^;]+)/);
99+
sRefreshToken = match ? match[1] : null;
100+
}
101+
response.headers.append("set-cookie", header);
102+
}
103+
104+
response.headers.append("set-cookie", `sFrontToken=${frontToken}`);
105+
response.headers.append("front-token", frontToken);
106+
response.headers.append("frontToken", frontToken);
107+
if (sAccessToken) {
108+
response.headers.append("sAccessToken", sAccessToken);
109+
110+
cookiesFromReq.set("sAccessToken", sAccessToken);
111+
}
112+
if (sRefreshToken) {
113+
response.headers.append("sRefreshToken", sRefreshToken);
114+
115+
cookiesFromReq.set("sRefreshToken", sRefreshToken);
116+
}
117+
118+
cookiesFromReq.set("sFrontToken", frontToken);
119+
120+
console.log(sAccessToken, sRefreshToken);
121+
122+
return response;
123+
} catch (err) {
124+
console.error("Error refreshing session");
125+
console.error(err);
126+
return NextResponse.redirect(new URL("/auth", request.url));
127+
}
128+
}
129+
130+
// async function saveTokensFromHeaders(response: Response) {
131+
// logDebugMessage("saveTokensFromHeaders: Saving updated tokens from the response headers");
132+
//
133+
// const refreshToken = response.headers.get("st-refresh-token");
134+
// if (refreshToken !== null) {
135+
// logDebugMessage("saveTokensFromHeaders: saving new refresh token");
136+
// await setToken("refresh", refreshToken);
137+
// }
138+
//
139+
// const accessToken = response.headers.get("st-access-token");
140+
// if (accessToken !== null) {
141+
// logDebugMessage("saveTokensFromHeaders: saving new access token");
142+
// await setToken("access", accessToken);
143+
// }
144+
//
145+
// const frontToken = response.headers.get("front-token");
146+
// if (frontToken !== null) {
147+
// logDebugMessage("saveTokensFromHeaders: Setting sFrontToken: " + frontToken);
148+
// await FrontToken.setItem(frontToken);
149+
// updateClockSkewUsingFrontToken({ frontToken, responseHeaders: response.headers });
150+
// }
151+
// const antiCsrfToken = response.headers.get("anti-csrf");
152+
// if (antiCsrfToken !== null) {
153+
// // At this point, the session has either been newly created or refreshed.
154+
// // Thus, there's no need to call getLocalSessionState with tryRefresh: true.
155+
// // Calling getLocalSessionState with tryRefresh: true will cause a refresh loop
156+
// // if cookie writes are disabled.
157+
// const tok = await getLocalSessionState(false);
158+
// if (tok.status === "EXISTS") {
159+
// logDebugMessage("saveTokensFromHeaders: Setting anti-csrf token");
160+
// await AntiCsrfToken.setItem(tok.lastAccessTokenUpdate, antiCsrfToken);
161+
// }
162+
// }
163+
// }

examples/with-next-ssr-app-directory/app/config/backend.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import SuperTokens from "supertokens-node";
1010

1111
export let backendConfig = (): TypeInput => {
1212
return {
13+
// debug: true,
1314
supertokens: {
1415
// this is the location of the SuperTokens core.
1516
connectionURI: "https://try.supertokens.com",

examples/with-next-ssr-app-directory/middleware.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { ensureSuperTokensInit } from "./app/config/backend";
77
ensureSuperTokensInit();
88

99
export async function middleware(request: NextRequest & { session?: SessionContainer }) {
10+
// if (request.nextUrl.pathname.startsWith("/api/refresh")) {
11+
// return refreshSession(request);
12+
// }
13+
1014
if (request.nextUrl.pathname.startsWith("/api")) {
1115
if (request.headers.has("x-user-id")) {
1216
console.warn(
@@ -24,6 +28,7 @@ export async function middleware(request: NextRequest & { session?: SessionConta
2428
if (err) {
2529
return NextResponse.json(err, { status: 500 });
2630
}
31+
2732
if (session === undefined) {
2833
return NextResponse.next();
2934
}
@@ -35,6 +40,11 @@ export async function middleware(request: NextRequest & { session?: SessionConta
3540
});
3641
}
3742

43+
const shouldRefresh = request.nextUrl.searchParams.get("forceRefresh") === "true";
44+
if (shouldRefresh) {
45+
return refreshSession(request);
46+
}
47+
3848
// Save the current path so that we can use it during SSR
3949
// Used to redirect the user to the correct path after login/refresh
4050
return NextResponse.next({
@@ -44,6 +54,83 @@ export async function middleware(request: NextRequest & { session?: SessionConta
4454
});
4555
}
4656

57+
const refreshTokenCookieName = "sRefreshToken";
58+
const refreshTokenHeaderName = "st-refresh-token";
59+
60+
async function refreshSession(request: NextRequest) {
61+
console.log("Attempting session refresh");
62+
const refreshToken =
63+
request.cookies.get(refreshTokenCookieName)?.value || request.headers.get(refreshTokenHeaderName);
64+
if (!refreshToken) {
65+
return NextResponse.redirect(new URL("/auth", request.url));
66+
}
67+
68+
const redirectTo = request.nextUrl.pathname;
69+
console.log(`Should redirect to ${redirectTo}`);
70+
try {
71+
const refreshResponse = await fetch(`http://localhost:3000/api/auth/session/refresh`, {
72+
method: "POST",
73+
headers: {
74+
"Content-Type": "application/json",
75+
Cookie: `sRefreshToken=${refreshToken}`,
76+
},
77+
credentials: "include",
78+
});
79+
console.log("Performed session refresh request");
80+
81+
const setCookieHeaders = refreshResponse.headers.getSetCookie();
82+
console.log(refreshResponse);
83+
console.log(refreshResponse.headers);
84+
console.log("Cookies", setCookieHeaders);
85+
86+
if (!setCookieHeaders.length) {
87+
return NextResponse.redirect(new URL("/auth", request.url));
88+
}
89+
90+
const frontToken = refreshResponse.headers.get("front-token");
91+
if (!frontToken) {
92+
return NextResponse.redirect(new URL("/auth", request.url));
93+
}
94+
95+
let sAccessToken: string | null = null;
96+
let sRefreshToken: string | null = null;
97+
98+
const redirectTo = new URL("/", request.url);
99+
const response = NextResponse.redirect(redirectTo);
100+
for (const header of setCookieHeaders) {
101+
if (header.includes("sAccessToken")) {
102+
const match = header.match(/sAccessToken=([^;]+)/);
103+
sAccessToken = match ? match[1] : null;
104+
}
105+
if (header.includes("sRefreshToken")) {
106+
const match = header.match(/sRefreshToken=([^;]+)/);
107+
sRefreshToken = match ? match[1] : null;
108+
}
109+
response.headers.append("set-cookie", header);
110+
}
111+
112+
response.headers.append("set-cookie", `sFrontToken=${frontToken}`);
113+
response.headers.append("front-token", frontToken);
114+
response.headers.append("frontToken", frontToken);
115+
if (sAccessToken) {
116+
response.headers.append("sAccessToken", sAccessToken);
117+
response.cookies.set("sAccessToken", sAccessToken);
118+
}
119+
if (sRefreshToken) {
120+
response.headers.append("sRefreshToken", sRefreshToken);
121+
122+
response.cookies.set("sRefreshToken", sRefreshToken);
123+
}
124+
125+
response.cookies.set("sFrontToken", frontToken);
126+
return response;
127+
} catch (err) {
128+
console.error("Error refreshing session");
129+
console.error(err);
130+
return NextResponse.redirect(new URL("/auth", request.url));
131+
}
132+
}
133+
47134
export const config = {
48135
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
49136
};

0 commit comments

Comments
 (0)