Skip to content

Commit 8231792

Browse files
committed
Revoke session inside the middleware
1 parent 46f93b1 commit 8231792

File tree

11 files changed

+105
-421
lines changed

11 files changed

+105
-421
lines changed

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

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,85 @@
11
import { NextResponse } from "next/server";
22
import type { NextRequest } from "next/server";
3-
import { SessionContainer } from "supertokens-node/recipe/session";
3+
import { SessionContainer, VerifySessionOptions } from "supertokens-node/recipe/session";
44
import { withSession } from "supertokens-node/nextjs";
55
import { ensureSuperTokensInit } from "./app/config/backend";
66
import { ssrConfig } from "./app/config/ssr";
7-
import { refreshSession } from "supertokens-auth-react/nextjs/middleware";
7+
import { refreshSession, revokeSession } from "supertokens-auth-react/nextjs/middleware";
8+
import { SuperTokensConfig } from "supertokens-auth-react/lib/build/types";
89

910
ensureSuperTokensInit();
1011

11-
export async function middleware(request: NextRequest & { session?: SessionContainer }) {
12-
if (request.nextUrl.pathname.startsWith("/api")) {
13-
if (request.headers.has("x-user-id")) {
14-
console.warn(
15-
"The FE tried to pass x-user-id, which is only supposed to be a backend internal header. Ignoring."
16-
);
17-
request.headers.delete("x-user-id");
18-
}
12+
const CURRENT_PATH_COOKIE_NAME = "sCurrentPath";
13+
const FORCE_LOGOUT_PATH_PARAM_NAME = "forceLogout";
1914

20-
if (request.nextUrl.pathname.startsWith("/api/auth/session/refresh") && request.method === "GET") {
21-
return refreshSession(ssrConfig(), request);
22-
}
15+
export default superTokensMiddleware(ssrConfig());
2316

24-
if (request.nextUrl.pathname.startsWith("/api/auth")) {
25-
// this hits our pages/api/auth/* endpoints
26-
return NextResponse.next();
17+
function superTokensMiddleware(
18+
config: SuperTokensConfig,
19+
options?: VerifySessionOptions
20+
): (request: NextRequest & { session?: SessionContainer }) => Promise<Response | void> {
21+
const usesNextjsForTheAuthenticationServer = true;
22+
23+
return async (request: NextRequest & { session?: SessionContainer }) => {
24+
if (request.nextUrl.pathname.startsWith("/api/auth/session/refresh") && request.method === "GET") {
25+
return refreshSession(config, request);
2726
}
2827

29-
return withSession(request, async (err, session) => {
30-
if (err) {
31-
return NextResponse.json(err, { status: 500 });
28+
if (request.nextUrl.pathname.startsWith("/api") && usesNextjsForTheAuthenticationServer) {
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");
3234
}
3335

34-
if (session === undefined) {
36+
if (request.nextUrl.pathname.startsWith("/api/auth")) {
37+
// this hits our pages/api/auth/* endpoints
3538
return NextResponse.next();
3639
}
37-
return NextResponse.next({
38-
headers: {
39-
"x-user-id": session.getUserId(),
40+
41+
return withSession(
42+
request,
43+
async (err, session) => {
44+
if (err) {
45+
return NextResponse.json(err, { status: 500 });
46+
}
47+
48+
if (session === undefined) {
49+
return NextResponse.next();
50+
}
51+
return NextResponse.next({
52+
headers: {
53+
"x-user-id": session.getUserId(),
54+
},
55+
});
4056
},
41-
});
42-
});
43-
}
44-
// Save the current path so that we can use it during SSR
45-
// Used to redirect the user to the correct path after login/refresh
46-
// https://github.com/vercel/next.js/issues/43704#issuecomment-2090798307
47-
// TL;DR: You can not access pathname in SSR and requests that originate from redirect()
48-
// do not have an origin header
49-
const response = new Response(null, {
50-
status: 200,
51-
});
52-
response.headers.append(
53-
"set-cookie",
54-
`sCurrentPath=${request.nextUrl.pathname}; Path=/; HttpOnly; SameSite=Strict`
55-
);
56-
return response;
57+
options
58+
);
59+
}
60+
61+
if (
62+
request.nextUrl.pathname.startsWith("/auth") &&
63+
request.nextUrl.searchParams.get(FORCE_LOGOUT_PATH_PARAM_NAME) === "true"
64+
) {
65+
return revokeSession(config);
66+
}
67+
68+
// Save the current path so that we can use it during SSR
69+
// Used to redirect the user to the correct path after login/refresh
70+
// https://github.com/vercel/next.js/issues/43704#issuecomment-2090798307
71+
// TL;DR: You can not access pathname in SSR and requests that originate from redirect()
72+
// do not have an origin header
73+
// const response = NextResponse.next();
74+
// response.cookies.set("sCurrentPath", request.nextUrl.pathname);
75+
const response = new Response(null, {});
76+
response.headers.set("x-middleware-next", "1");
77+
response.headers.append(
78+
"set-cookie",
79+
`${CURRENT_PATH_COOKIE_NAME}=${request.nextUrl.pathname}; Path=/; HttpOnly; SameSite=Strict`
80+
);
81+
return response;
82+
};
5783
}
5884

5985
export const config = {

lib/ts/custom.ts

Lines changed: 0 additions & 98 deletions
This file was deleted.

lib/ts/nextjs/middleware.ts

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,60 +13,11 @@ const ANTI_CSRF_TOKEN_HEADER_NAME = "anti-csrf";
1313

1414
const REDIRECT_ATTEMPT_MAX_COUNT = 5;
1515
const REDIRECT_PATH_PARAM_NAME = "stRedirectTo";
16+
const FORCE_LOGOUT_PATH_PARAM_NAME = "forceLogout";
1617
const REDIRECT_ATTEMPT_COUNT_COOKIE_NAME = "sSsrSessionRefreshAttempt";
1718

1819
let AppInfo: SuperTokensNextjsConfig["appInfo"];
1920

20-
// export async function superTokensMiddeware(config: SuperTokensNextjsConfig): () => Promise<Response | void> {
21-
// AppInfo = config.appInfo;
22-
// if (config.enableDebugLogs) {
23-
// enableLogging();
24-
// }
25-
//
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-
// };
68-
// }
69-
7021
export async function refreshSession(config: SuperTokensNextjsConfig, request: Request): Promise<Response> {
7122
AppInfo = config.appInfo;
7223
if (config.enableDebugLogs) {
@@ -134,9 +85,45 @@ export async function refreshSession(config: SuperTokensNextjsConfig, request: R
13485
}
13586
}
13687

88+
export async function revokeSession(config: SuperTokensNextjsConfig): Promise<Response | void> {
89+
AppInfo = config.appInfo;
90+
if (config.enableDebugLogs) {
91+
enableLogging();
92+
}
93+
const signOutURL = new URL(`${AppInfo.apiBasePath}/signout`, AppInfo.apiDomain);
94+
try {
95+
const signOutResponse = await fetch(signOutURL, {
96+
method: "POST",
97+
headers: {
98+
"Content-Type": "application/json",
99+
},
100+
credentials: "include",
101+
});
102+
103+
if (signOutResponse.status !== 200) {
104+
return;
105+
}
106+
107+
const response = new Response(null, {});
108+
response.headers.set("x-middleware-next", "1");
109+
// TODO: Delete only the cookies that are auth related
110+
response.headers.delete("set-cookie");
111+
response.headers.delete(ACCESS_TOKEN_HEADER_NAME);
112+
response.headers.delete(REFRESH_TOKEN_HEADER_NAME);
113+
response.headers.delete(FRONT_TOKEN_HEADER_NAME);
114+
response.headers.delete(ANTI_CSRF_TOKEN_HEADER_NAME);
115+
return response;
116+
} catch (err) {
117+
logDebugMessage("Error revoking session");
118+
logDebugMessage(err as unknown as string);
119+
return;
120+
}
121+
}
122+
137123
function redirectToAuthPage(request: Request): Response {
138124
const authPagePath = AppInfo.websiteBasePath || "/auth";
139125
const redirectUrl = new URL(authPagePath, request.url);
126+
redirectUrl.searchParams.set(FORCE_LOGOUT_PATH_PARAM_NAME, "true");
140127
logDebugMessage(`Redirecting to: ${redirectUrl}`);
141128
return new Response(null, {
142129
status: 307,

lib/ts/nextjs/ssr.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const FRONT_TOKEN_NAME = "sFrontToken";
1313
const FRONT_TOKEN_HEADER_NAME = "front-token";
1414

1515
const CURRENT_PATH_COOKIE_NAME = "sCurrentPath";
16+
const FORCE_LOGOUT_PATH_PARAM_NAME = "forceLogout";
1617
const REDIRECT_PATH_PARAM_NAME = "stRedirectTo";
1718

1819
type SSRSessionState =
@@ -114,7 +115,8 @@ export const getSSRSession = SuperTokensNextjsSSRAPIWrapper.getSSRSession;
114115
export const getServerSidePropsSession = SuperTokensNextjsSSRAPIWrapper.getServerSidePropsSession;
115116

116117
function getAuthPagePath(): string {
117-
return SuperTokensNextjsSSRAPIWrapper.getConfigOrThrow().appInfo.websiteBasePath || "/auth";
118+
const authPagePath = SuperTokensNextjsSSRAPIWrapper.getConfigOrThrow().appInfo.websiteBasePath || "/auth";
119+
return `${authPagePath}?${FORCE_LOGOUT_PATH_PARAM_NAME}=true`;
118120
}
119121

120122
function getRefreshLocation(): string {

lib/ts/recipe/session/framework/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)