Reputation: 13
I want to get the login status of any request in the middleware and I want to redirect that to the protected route or the login route.
But I am not getting the session inside the middleware.ts
. I used this code but still showing the error.
//auth.ts
import NextAuth from "next-auth"
export const { auth, handlers } = NextAuth({
callbacks: {
authorized: async ({ auth }) => {
// Logged in users are authenticated, otherwise redirect to login page
return !!auth
},
},
})
// middleware.ts
import { auth } from "@/auth"
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
Upvotes: 0
Views: 401
Reputation: 1
For starters, migrate to AuthJS v5. Second, the issue doesn't seem to be from the next-auth, instead you don't have aws-sdk installed and that's why the error.
Assuming you are implementing RBAC (Role Based Access control), this middleware can work for you:
//middleware.ts
// Importing necessary functions from the "next/server" module
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// Importing the auth function from our auth.ts file
import { auth } from "@/auth";
// Defining the protected routes with the roles allowed to access them, including dynamic segments
const protectedRoutes = [
{ path: "/admin/:path", roles: ["ADMIN"] }, // Only ADMIN can access this route
{ path: "/user/:path", roles: ["ADMIN", "USER"] }, // Both ADMIN and USER can access this route
];
// Function to find the current route in the protectedRoutes array
const findCurrentRoute = (pathname: string) => {
return protectedRoutes.find((route) =>
new RegExp(`^${route.path.replace(/:\w+/g, "\\w+")}$`).test(pathname)
);
};
// Middleware function to handle requests
export default async function middleware(request: NextRequest) {
// Get the session using the auth function
const session = await auth();
// Find the current route in the protectedRoutes array
const currentRoute = findCurrentRoute(request.nextUrl.pathname);
// Check if the request path is protected
if (currentRoute) {
// If the route is protected and there's no session, redirect to the login page
if (!session) {
const absoluteURL = new URL("/auth/login", request.nextUrl.origin);
return NextResponse.redirect(absoluteURL.toString());
}
// If the user role is not allowed for the current route, redirect to the unauthorized page
if (!currentRoute.roles.includes(session.user.role)) {
const absoluteURL = new URL("/unauthorized", request.nextUrl.origin);
return NextResponse.redirect(absoluteURL.toString());
}
}
// If the route is not protected or the user has access, continue to the next middleware or the requested page
return NextResponse.next();
}
// Configuration to match all paths except for certain static files and API routes
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Also, here's a beginner level configured auth.config.ts and auth.ts with Github, Google, and Credentials based authentication. Edge case compatible, if you are going to deploy on vercel.
//auth.config.ts
import Github from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import { type NextAuthConfig } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import bcrypt from "bcrypt-edge";
import { prisma } from "./prisma";
// Adding the callbacks for managing the session and token
export default {
providers: [
Github({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
allowDangerousEmailAccountLinking: true
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
allowDangerousEmailAccountLinking: true
}),
Credentials({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "[email protected]",
},
password: { label: "Password", type: "password" },
},
authorize: async (credentials) => {
if (!credentials || !credentials.email || !credentials.password) {
return null;
}
const email = credentials.email as string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = await prisma.user.findUnique({
where: {
email,
},
});
if (!user) {
throw new Error("No account found with this email.");
} else {
const isMatch = bcrypt.compareSync(
credentials.password as string,
user.hashedPassword
);
if (!isMatch) {
throw new Error("Incorrect password.");
}
}
// Return user object with role information
return { ...user, role: user.role };
},
}),
],
callbacks: {
// Callback to manage the JWT token
jwt: async ({ token, user }) => {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
// Callback to manage the session object
session: async ({ session, token }) => {
if (token) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
},
},
// Additional configuration can go here (e.g., pages)
pages: {
signIn: "/sign-in",
},
} satisfies NextAuthConfig;
//auth.ts
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "./prisma"
import authConfig from "./auth.config";
export const {
handlers: { GET, POST },
signIn,
signOut,
auth,
} = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "jwt" },
...authConfig,
});
And the code for salting and hashing using bcrypt (bcrypt-edge):
import bcrypt from "bcrypt-edge";
export function saltAndHashPassword(password: any) {
const saltRounds = 10; // Adjust the cost factor according to your security requirements
const salt = bcrypt.genSaltSync(saltRounds); // Synchronously generate a salt
const hash = bcrypt.hashSync(password, salt); // Synchronously hash the password
return hash; // Return the hash directly as a string
}
You will probably get TypeScript error RBAC in auth.config.ts, here's how you can fix it:
import { DefaultSession, DefaultUser } from "@auth";
// Extend the User interface to include role
declare module "next-auth" {
interface User extends DefaultUser {
role?: string;
}
interface Session {
user?: {
id?: string;
role?: string;
} & DefaultSession["USER"];
}
}
Make sure that your user does have the role field. Example schema.prisma:
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
email String? @unique
hashedPassword String?
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
role UserRole? @default(USER)
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
blogPosts BlogPost[]
}
Also, you will have to make sure that your database has the necessary schema for these to work, you can get these here (select your connection method to database): AuthJS v5 database adapters
Upvotes: 0