[GH-ISSUE #3431] How to access admin / roles in nextjs middleware ? #18233

Closed
opened 2026-04-15 16:38:04 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @pranavmappoli on GitHub (Jul 17, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3431

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Below is my middleware & im using better-auth admin plugin

now, when a normal user try to access the route, the route should be blocked by middleware in nextjs

I have tried the below, but it not working, Is there any way for this

import { getSessionCookie } from "better-auth/cookies";
import { NextRequest, NextResponse } from "next/server";
import { validateUser } from "./actions/user";
import { UserRole } from "./db/schema/enums";
import {
	adminRoutes,
	apiAuthRoutes,
	authRoutes,
	defaultLoginRedirect,
	privateRoutes,
} from "./utils/routes";

export default async function middleware(request: NextRequest) {
	const { nextUrl } = request;
	const sessionCookie = getSessionCookie(request);

	// Don't block API authentication routes
	if (nextUrl.pathname.startsWith(apiAuthRoutes)) {
		return NextResponse.next();
	}

	// Handle login page route - allow access regardless of auth status
	if (nextUrl.pathname === "/login") {
		return NextResponse.next();
	}

	// Handle auth routes (signin, signup, resetPassword)
	if (authRoutes.includes(nextUrl.pathname)) {
		// If user is logged in and tries to access auth routes, redirect to default
		if (sessionCookie) {
			return NextResponse.redirect(new URL(defaultLoginRedirect, nextUrl));
		}
		return NextResponse.next();
	}

	// Handle private routes - redirect to login page if no session cookie
	if (privateRoutes.some((route) => nextUrl.pathname.startsWith(route))) {
		if (!sessionCookie) {
			const loginUrl = new URL("/login", nextUrl);
			loginUrl.searchParams.set(
				"message",
				"Please sign in to access this page.",
			);
			return NextResponse.redirect(loginUrl);
		}
	}

	// Handle admin routes - check if user is authenticated and has admin role
	if (adminRoutes.some((route) => nextUrl.pathname.startsWith(route))) {
		// If no session cookie, show 404 (don't reveal admin routes exist)
		if (!sessionCookie) {
			return NextResponse.rewrite(new URL("/not-found", nextUrl));
		}

		// Get user session to check role
		try {
			const user = await validateUser();

			// If no valid user, show 404
			if (!user) {
				return NextResponse.rewrite(new URL("/not-found", nextUrl));
			}

			const userRole = user.role;
			const isAdmin = userRole === UserRole.ADMIN;

			// If not admin, show 404 (don't reveal admin routes exist)
			if (!isAdmin) {
				return NextResponse.rewrite(new URL("/not-found", nextUrl));
			}
		} catch (error) {
			console.error("Error checking admin access:", error);
			// On error, show 404 (don't reveal admin routes exist)
			return NextResponse.rewrite(new URL("/not-found", nextUrl));
		}
	}

	return NextResponse.next();
}

export const config = {
	// runtime: "nodejs",
	matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};


### Current vs. Expected behavior

Should able to check isAdmin or not
Not able to check

### What version of Better Auth are you using?

latest

### Provide environment information

```bash
chrome

Which area(s) are affected? (Select all that apply)

Client

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

No response

Originally created by @pranavmappoli on GitHub (Jul 17, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3431 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Below is my middleware & im using better-auth admin plugin now, when a normal user try to access the route, the route should be blocked by middleware in nextjs I have tried the below, but it not working, Is there any way for this ``` import { getSessionCookie } from "better-auth/cookies"; import { NextRequest, NextResponse } from "next/server"; import { validateUser } from "./actions/user"; import { UserRole } from "./db/schema/enums"; import { adminRoutes, apiAuthRoutes, authRoutes, defaultLoginRedirect, privateRoutes, } from "./utils/routes"; export default async function middleware(request: NextRequest) { const { nextUrl } = request; const sessionCookie = getSessionCookie(request); // Don't block API authentication routes if (nextUrl.pathname.startsWith(apiAuthRoutes)) { return NextResponse.next(); } // Handle login page route - allow access regardless of auth status if (nextUrl.pathname === "/login") { return NextResponse.next(); } // Handle auth routes (signin, signup, resetPassword) if (authRoutes.includes(nextUrl.pathname)) { // If user is logged in and tries to access auth routes, redirect to default if (sessionCookie) { return NextResponse.redirect(new URL(defaultLoginRedirect, nextUrl)); } return NextResponse.next(); } // Handle private routes - redirect to login page if no session cookie if (privateRoutes.some((route) => nextUrl.pathname.startsWith(route))) { if (!sessionCookie) { const loginUrl = new URL("/login", nextUrl); loginUrl.searchParams.set( "message", "Please sign in to access this page.", ); return NextResponse.redirect(loginUrl); } } // Handle admin routes - check if user is authenticated and has admin role if (adminRoutes.some((route) => nextUrl.pathname.startsWith(route))) { // If no session cookie, show 404 (don't reveal admin routes exist) if (!sessionCookie) { return NextResponse.rewrite(new URL("/not-found", nextUrl)); } // Get user session to check role try { const user = await validateUser(); // If no valid user, show 404 if (!user) { return NextResponse.rewrite(new URL("/not-found", nextUrl)); } const userRole = user.role; const isAdmin = userRole === UserRole.ADMIN; // If not admin, show 404 (don't reveal admin routes exist) if (!isAdmin) { return NextResponse.rewrite(new URL("/not-found", nextUrl)); } } catch (error) { console.error("Error checking admin access:", error); // On error, show 404 (don't reveal admin routes exist) return NextResponse.rewrite(new URL("/not-found", nextUrl)); } } return NextResponse.next(); } export const config = { // runtime: "nodejs", matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], }; ### Current vs. Expected behavior Should able to check isAdmin or not Not able to check ### What version of Better Auth are you using? latest ### Provide environment information ```bash chrome ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-15 16:38:04 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 17, 2025):

To check admin roles in Next.js middleware with Better Auth, you can't safely use custom helpers like validateUser in edge middleware—these often require DB or API access, which isn't supported in that environment. Instead, you have two main options:

1. Use getCookieCache (if session cookie caching is enabled):
If your Better Auth config enables session cookie caching, you can access the session (including user roles) directly from the cookie in middleware. Example:

import { getCookieCache } from "better-auth/cookies";
import { NextRequest, NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
  const session = await getCookieCache(request);
  const isAdmin = session?.user?.role === "admin";
  // ...your logic here
}

Make sure your session data is small enough to fit in a cookie, or you'll hit size limits.
Docs: getCookieCache usage

2. Use Node.js middleware (Next.js 15.2.0+):
If you're on Next.js 15.2.0 or newer, you can set your middleware to use the Node.js runtime. This lets you access the full session (and roles) directly, e.g. via auth.api.
Docs: Node.js runtime in middleware

Why your current approach fails:
validateUser likely makes DB/API calls, which aren't supported in edge middleware. Stick to cookie-based checks or switch to Node.js runtime if you need more.

Tip:
If you customized your cookie name/prefix in your auth config, pass the same config to getSessionCookie/getCookieCache to ensure they match your setup.

Let me know if you need a full code example for your specific setup!

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

<!-- gh-comment-id:3084531930 --> @dosubot[bot] commented on GitHub (Jul 17, 2025): <!-- Answer --> To check admin roles in Next.js middleware with Better Auth, you can't safely use custom helpers like validateUser in edge middleware—these often require DB or API access, which isn't supported in that environment. Instead, you have two main options: **1. Use getCookieCache (if session cookie caching is enabled):** If your Better Auth config enables session cookie caching, you can access the session (including user roles) directly from the cookie in middleware. Example: ```ts import { getCookieCache } from "better-auth/cookies"; import { NextRequest, NextResponse } from "next/server"; export async function middleware(request: NextRequest) { const session = await getCookieCache(request); const isAdmin = session?.user?.role === "admin"; // ...your logic here } ``` Make sure your session data is small enough to fit in a cookie, or you'll hit size limits. [Docs: getCookieCache usage](https://github.com/better-auth/better-auth/blob/0c4583c015807077cbded89e089f59f7d3d67b48/docs/content/docs/integrations/next.mdx) **2. Use Node.js middleware (Next.js 15.2.0+):** If you're on Next.js 15.2.0 or newer, you can set your middleware to use the Node.js runtime. This lets you access the full session (and roles) directly, e.g. via auth.api. [Docs: Node.js runtime in middleware](https://better-auth.com/docs/integrations/next) **Why your current approach fails:** validateUser likely makes DB/API calls, which aren't supported in edge middleware. Stick to cookie-based checks or switch to Node.js runtime if you need more. **Tip:** If you customized your cookie name/prefix in your auth config, pass the same config to getSessionCookie/getCookieCache to ensure they match your setup. Let me know if you need a full code example for your specific setup! <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/7e7eced4-03eb-4f22-b5ac-1c0e41e339de?feedback_type=other)</sup>&nbsp;&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3431)
Author
Owner

@Maqed commented on GitHub (Jul 18, 2025):

In addition to dosubot's response, you can also get the session using fetch function.

You can refer this this: https://www.better-auth.com/docs/integrations/next#for-nextjs-release-1517-and-below

<!-- gh-comment-id:3086365104 --> @Maqed commented on GitHub (Jul 18, 2025): In addition to dosubot's response, you can also get the session using fetch function. You can refer this this: https://www.better-auth.com/docs/integrations/next#for-nextjs-release-1517-and-below
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#18233