I am trying to implement a system where user will redirected to previous protected dynamic route after successful login. Let me explain briefly. First I provide you the folder structure, Login component and Nextauth settings
Using
"next-auth": "^4.22.3",
"next": "13.4.5",
Here is the folder structure for better understanding
π¦src
β£ πapp
β β£ π(auth)
β β β£ πlogin
β β β β πpage.tsx
β β β£ πregister
β β β β πpage.tsx
β β£ π(private_route)
β β β£ πanimated-card
β β β β π[id]
β β β β β£ πpage.jsx
β β β β β£ πreadme.md
β β β β β πreadme2.md
β β β£ πimage-editor
β β β β π[id]
β β β β β£ πpage.jsx
β β β£ πlet-us-design-for-you
β β β β π[id]
β β β β β πpage.jsx
β β£ πapi
β β β£ πauth
β β β β£ πusers
β β β β β πroute.js
β β β β π[...nextauth]
β β β β β πroute.js
api/auth/[...nextauth]/route.js
import startDb from "@/lib/db";
import UserModel from "../../../../models/userModel";
import NextAuth, { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import FacebookProvider from "next-auth/providers/facebook";
export const authOptions = {
session: {
strategy: "jwt",
},
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
FacebookProvider({
clientId: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
}),
CredentialsProvider({
name: "Credentials",
credentials: {},
async authorize(credentials, req) {
const { email, password } = credentials;
await startDb();
const user = await UserModel.findOne({ email });
if (!user) throw new Error("email/password mismatch!");
const passwordMatch = await user.comparePassword(password);
if (!passwordMatch) throw new Error("email/password mismatch");
return {
id: user._id.toString(),
email: user.email,
name: user.name,
role: user.role,
};
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
pages: {
signIn: "/login",
signOut: "/login",
error: "/login",
},
callbacks: {
async session({ session, token }) {
if (session.user) {
session.user.id = token.id;
session.user.role = token.role;
}
return session;
},
jwt(params) {
if (params.user?.role) {
params.token.role = params.user.role;
params.token.id = params.user.id;
}
return params.token;
},
},
};
const authHandler = NextAuth(authOptions);
module.exports = {
GET: authHandler,
POST: authHandler,
};
and Finally the Login Component
"use client";
import Image from "next/image";
import React, { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
const Login = () => {
const router = useRouter();
const [showToast, setShowToast] = useState(false);
const [type, setType] = useState("");
const [busy, setBusy] = useState(false);
const [userInfo, setUserInfo] = useState({
email: "",
password: "",
});
const [error, setError] = useState("");
const { password, email } = userInfo;
const handleChange = ({ target }) => {
const { name, value } = target;
setUserInfo({ ...userInfo, [name]: value });
};
const handleSubmit = async (e) => {
e.preventDefault();
// console.log(userInfo);
const res = await signIn("credentials", {
email,
password,
redirect: false,
});
if (res?.error) {
setType("error");
setError("Login Failed.");
setShowToast(true);
return;
}
// If login is successful
setType("success");
setError("Login Success.");
setShowToast(true);
router.push("/");
};
const handleToastClose = () => {
setShowToast(false);
};
return (
<div>
<form onSubmit={handleSubmit}>
<div className="form-control join-item text-base">
<div className="mb-2">
<label className="label">
<span className="text-base">Username or Email *</span>
</label>
<input
type="text"
onChange={handleChange}
value={email}
name="email"
placeholder="[email protected]"
className="input input-bordered w-full h-11 placeholder:italic placeholder:font-normal"
/>
</div>
<div>
<label className="label">
<span className="text-base">Password *</span>
</label>
<input
type="password"
value={password}
name="password"
onChange={handleChange}
placeholder="Password at least 6 Characters long"
className="input input-bordered w-full h-11 placeholder:italic placeholder:font-normal"
/>
</div>
<button className="btn btn-primary mt-4 mb-6 text-white text-base capitalize font-medium">
Sign In
</button>
</div>
</form>
</div>
);
};
export default Login;
When a non-user attempts to access these protected routes, they are sent to the login page. These are the protected routes
After successful login they will be redirected to home page. But I dont want that. I want them to redirect them to the protected page where they were last redirected.
I tried to follow the next auth documentation. But did not understand.
I guess the next auth is having some bugs with their app router version. One approach is to set a callbackUrl for each pages and you have to do that on the page level instead of doing it on the layout level. I'm sharing a sample code (assuming that all your pages are server-component):
(private_route)/animated-card/[id]/page.jsx
import { getServerSession } from 'next-auth/next'
import { redirect } from 'next/navigation'
import { authOptions } from "../api/auth/[...nextauth]/route";
const ProtectedPage = async ({params}) => {
const session = await getServerSession(authOptions)
if (!session) {
redirect(`/login?callbackUrl=/animated-card/${params.id}`)
}
return (
<section className='py-24'>
<div className='container'>
Hello from animated card{params.id}..
</div>
</section>
)
}
export default ProtectedPage
Here, we're passing the url of the specific page that user is trying to access as a callback url and after successful login, user will be redirected to that url.
checkout this git repo for more: https://github.com/foadtkf/next-auth-playground