Authentication & Authorization
NextAdmin provides a robust foundation for Authentication using Better Auth ready to be shipped out of the box.
Authentication and OAuth Providers
NextAdmin comes with built in Email & Password and Google authentication, which covers most use cases. If you want to extend the authentication and introduce other OAuth methods, follow the guide here (opens in a new tab).
To add new OAuth providers, simply update the src/lib/auth.ts file with your desired provider configuration.
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID || "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
},
// Add more providers here
github: {
clientId: process.env.GITHUB_CLIENT_ID || "",
clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
},
}Now run the following command to apply the changes into your prisma schema:
npm run auth:generateThis will create a new better-auth-schema.prisma file into /prisma directory. You can copy and paste the generated schema into your main schema.prisma file.
Now run db:migrate command to apply the changes to your database.
npm run db:migrateNOTE: We intentionally configured to generate a separate schema file for better auth to give you the flexibility to customize the generated schema as you want. You can also choose to keep the generated schema file and import it into your main
schema.prismafile or simply delete it.
To generate the schema into your main schema.prisma file, update the --output flag in the package.json:
"scripts": {
"dev": "next dev",
"build": "prisma generate && next build",
"start": "next start",
"lint": "next lint",
"db:migrate": "prisma migrate dev",
"db:generate": "prisma generate",
"auth:generate": "npx auth@latest generate --output ./prisma/schema.prisma"
}Creating New Roles and Permissions
The core of our authorization system is built around user roles defined in your database using Better Auth's Admin (opens in a new tab) plugin. These roles determine what a user can see and do within the dashboard. The roles and permissions are defined in the src/lib/auth/permissions.ts file:
import { createAccessControl } from "better-auth/plugins/access";
import { adminAc, defaultStatements } from "better-auth/plugins/admin/access";
// Defining new permissions
export const statement = {
...defaultStatements,
payment: ["get", "list", "create", "update", "delete"],
marketing: ["get", "list", "create", "update", "delete"],
crm: ["get", "list", "create", "update", "delete"],
} as const;
export const ac = createAccessControl(statement);
// Creating new roles
export const visitor = ac.newRole({
user: ["get", "list"],
payment: ["get", "list"],
marketing: ["get", "list"],
crm: ["get", "list"],
});
export const admin = ac.newRole({
...adminAc.statements,
payment: ["get", "list", "create", "update", "delete"],
marketing: ["get", "list", "create", "update", "delete"],
crm: ["get", "list", "create", "update", "delete"],
});
export const availableRoles = ["user", "visitor", "admin"] as const;
export type UserRole = (typeof availableRoles)[number];How it Works
- Define Permissions: Define the permissions (opens in a new tab) you need under resources (a fancy word for collection).
- Create Roles: Now create new roles (opens in a new tab) with the permissions each role will have under resources.
Now pass the new roles (opens in a new tab) into your auth.ts and auth-client.ts files:
plugins: [
// Other plugins...
adminPlugin({
ac,
roles: {
admin,
visitor,
user: userAc,
},
}),
],plugins: [
// Other plugins...
adminPlugin({
ac,
roles: {
admin,
visitor,
user: userAc,
},
}),
],Role-Based Access Control (RBAC)
In Next JS applications, RBAC is typically handled in the proxy.ts (or middleware.ts) file. But Better Auth recommends implementing Page Level authorization for better performance.
Page Level Authorization
You can check if the user is signed in using the auth.api.getSession() method in server components or authClient.useSession() in client components:
const session = await auth.api.getSession({
headers: await headers(),
});
// Redirect to sign-in page if not authenticated
if (!session?.user) {
redirect(`/auth/sign-in?callbackUrl=/`);
}"use client";
import { useSession } from "@/lib/auth-client";
export default function Profile() {
const { data: session, isPending, error } = useSession();
if (isPending) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!session) return <p>Not logged in</p>;
return (
<div>
<p>Welcome, {session.user.name}</p>
<p>Email: {session.user.email}</p>
</div>
);
}Restricting Feature Access to Specific Roles
If you want to give access to certain features for specific roles, you can use the hasPermission() method we provided. It is a fully type safe utility function.
import hasPermission from "@/utils/hasPermission";
export default async function AnalyticsPage(props: PageProps) {
const permission = await hasPermission({
marketing: ["get", "list"],
});
// Redirect if does not have permission
if (!permission) {
return redirect(
`/?toast_type=error&message=${encodeURIComponent(
"You don't have permission to access Analytics page",
)}`,
);
}
return (
<div>
<h1>Analytics Page</h1>
{/* Your analytics content */}
</div>
);
}Middleware/Proxy Configuration
Although it is discouraged to perform authorization checks in the middleware for better performance, you can still do it if you want to restrict access to certain routes. An example example.proxy.ts file is provided in the src directory for your reference.
import { getSessionCookie } from "better-auth/cookies";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
export async function proxy(req: NextRequest) {
const pathname = req.nextUrl.pathname;
// Better Auth recommends page-level authorization for real security.
// This proxy only performs a fast cookie presence check to keep redirects
// snappy without hitting the database in edge-like runtimes.
const sessionCookie = getSessionCookie(req);
if (
(pathname.includes("/admin") || pathname.includes("/user")) &&
!sessionCookie
) {
return NextResponse.redirect(new URL("/auth/sign-in", req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/user/:path*", "/admin/:path*"],
};