feat: add cookie helper for middlewares (#1275)

This commit is contained in:
Bereket Engida
2025-02-04 10:14:03 +03:00
committed by GitHub
parent 04ef8e8436
commit 2eca1daf4f
5 changed files with 120 additions and 34 deletions

View File

@@ -1,20 +1,9 @@
import { betterFetch } from "@better-fetch/fetch";
import { NextRequest, NextResponse } from "next/server";
import type { Session } from "./lib/auth-types";
import { getSessionCookie } from "better-auth";
export async function middleware(request: NextRequest) {
const { data: session } = await betterFetch<Session>(
"/api/auth/get-session",
{
baseURL: request.nextUrl.origin,
headers: {
//get the cookie from the request
cookie: request.headers.get("cookie") || "",
},
},
);
if (!session) {
const cookies = getSessionCookie(request);
if (!cookies) {
return NextResponse.redirect(new URL("/", request.url));
}
return NextResponse.next();

View File

@@ -127,40 +127,56 @@ const signIn = async () => {
## Middleware
In Next.js, middleware doesnt have access to many Node APIs, so you cant use the usual `auth` instance to validate sessions directly. Instead, you can make a request to the API route to get the session using the request headers.
In Next.js middleware, it's recommended to only check for the existence of a session cookie to handle redirection. To avoid blocking requests by making API or database calls.
Heres how it looks:
<Callout>
We're using `better-fetch` to make the request to the API route. You can use any fetch library you want.
</Callout>
You can use the `getSessionCookie` helper from Better Auth for this purpose:
```ts
import { betterFetch } from "@better-fetch/fetch";
import type { auth } from "@/lib/auth";
import { NextResponse, type NextRequest } from "next/server";
import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth";
type Session = typeof auth.$Infer.Session;
export default async function authMiddleware(request: NextRequest) {
const { data: session } = await betterFetch<Session>(
"/api/auth/get-session",
{
baseURL: request.nextUrl.origin,
headers: {
//get the cookie from the request
cookie: request.headers.get("cookie") || "",
},
},
);
if (!session) {
return NextResponse.redirect(new URL("/sign-in", request.url));
export async function middleware(request: NextRequest) {
const sessionCookie = getSessionCookie(request); // Optionally pass config as the second argument if cookie name or prefix is customized.
if (!sessionCookie) {
return NextResponse.redirect(new URL("/", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard"],
matcher: ["/dashboard"], // Specify the routes the middleware applies to
};
```
If you need the full session object, you'll have to fetch it from the `/get-session` API route. Since Next.js middleware doesn't support running Node.js APIs directly, you must make an HTTP request.
<Callout>
The example uses [better-fetch](https://better-fetch.vercel.app), but you can use any fetch library.
</Callout>
```ts
import { betterFetch } from "@better-fetch/fetch";
import type { auth } from "@/lib/auth";
import { NextRequest, NextResponse } from "next/server";
type Session = typeof auth.$Infer.Session;
export async function middleware(request: NextRequest) {
const { data: session } = await betterFetch<Session>("/api/auth/get-session", {
baseURL: request.nextUrl.origin,
headers: {
cookie: request.headers.get("cookie") || "", // Forward the cookies from the request
},
});
if (!session) {
return NextResponse.redirect(new URL("/sign-in", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard"], // Apply middleware to specific routes
};
```

View File

@@ -8,6 +8,7 @@ import type {
InferUser,
PrettifyDeep,
Expand,
AuthContext,
} from "./types";
import { getBaseURL } from "./utils/url";
import type { FilterActions, InferAPI } from "./types";
@@ -67,4 +68,5 @@ export type Auth = {
api: FilterActions<ReturnType<typeof router>["endpoints"]>;
options: BetterAuthOptions;
$ERROR_CODES: typeof BASE_ERROR_CODES;
$context: Promise<AuthContext>;
};

View File

@@ -0,0 +1,23 @@
import { parseCookies } from "../cookies";
import type { AuthContext } from "../types";
export const checkAuthCookie = async (
request: Request | Headers,
auth: {
$context: Promise<AuthContext>;
},
) => {
const headers = request instanceof Headers ? request : request.headers;
const cookies = headers.get("cookie");
if (!cookies) {
return null;
}
const ctx = await auth.$context;
const cookieName = ctx.authCookies.sessionToken.name;
const parsedCookie = parseCookies(cookies);
const sessionToken = parsedCookie.get(cookieName);
if (sessionToken) {
return sessionToken;
}
return null;
};

View File

@@ -1,6 +1,6 @@
import type { CookieOptions } from "better-call";
import { BetterAuthError } from "../error";
import type { Session, User } from "../types";
import type { AuthContext, Session, User } from "../types";
import type { GenericEndpointContext } from "../types/context";
import type { BetterAuthOptions } from "../types/options";
import { getDate } from "../utils/date";
@@ -8,6 +8,7 @@ import { isProduction } from "../utils/env";
import { base64Url } from "@better-auth/utils/base64";
import { createTime } from "../utils/time";
import { createHMAC } from "@better-auth/utils/hmac";
import { safeJSONParse } from "../utils/json";
export function createCookieGetter(options: BetterAuthOptions) {
const secure =
@@ -213,4 +214,59 @@ export function parseCookies(cookieHeader: string) {
export type EligibleCookies = (string & {}) | (keyof BetterAuthCookies & {});
export const getSessionCookie = (
request: Request | Headers,
config?: {
cookiePrefix?: string;
cookieName?: string;
},
) => {
const headers = request instanceof Headers ? request : request.headers;
const cookies = headers.get("cookie");
if (!cookies) {
return null;
}
const { cookieName = "session_token", cookiePrefix = "better-auth" } =
config || {};
const name = isProduction
? `__Secure-${cookiePrefix}.${cookieName}`
: `${cookiePrefix}.${cookieName}`;
const parsedCookie = parseCookies(cookies);
const sessionToken = parsedCookie.get(name);
if (sessionToken) {
return sessionToken;
}
return null;
};
export const getCookieCache = <
Session extends {
session: Session & Record<string, any>;
user: User & Record<string, any>;
},
>(
request: Request | Headers,
config?: {
cookiePrefix?: string;
cookieName?: string;
},
) => {
const headers = request instanceof Headers ? request : request.headers;
const cookies = headers.get("cookie");
if (!cookies) {
return null;
}
const { cookieName = "session_data", cookiePrefix = "better-auth" } =
config || {};
const name = isProduction
? `__Secure-${cookiePrefix}.${cookieName}`
: `${cookiePrefix}.${cookieName}`;
const parsedCookie = parseCookies(cookies);
const sessionData = parsedCookie.get(name);
if (sessionData) {
return safeJSONParse<Session>(sessionData);
}
return null;
};
export * from "./cookie-utils";