next-auth 4 session returns null, next.js - reactjs

This is code in api/auth/[...nextAuth].js
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import User from "#/models/user";
import connectToDb from "#/config/dbConnection";
export default NextAuth({
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
async authorize(credentials) {
connectToDb();
const { email, password } = credentials;
// check if email and passwird entered
if (!email || !password) {
throw new Error("please enter email or password");
}
const user = await User.findOne({
email,
// becasue in pre.save I excluded returning password in api response
}).select("+password");
if (!user) {
throw new Error("Invalid email or password");
}
// check if password is correct
// I added comparePassword to userSchema methods in mongoose
const isPasswordMatched = await user.comparePassword(password);
if (!isPasswordMatched) {
throw new Error("please enter email or password");
}
// creating a promise
return Promise.resolve(user);
},
}),
],
callbacks: {
// jwt callback is only called when token is created
jwt: async ({ token, user }) => {
// user is obj that we have received from authorize Promise.resolve(user)
user && (token.user = user);
// not this token has user property
return Promise.resolve(token);
},
// user arg here is actully token that returned from jwt.
session: async ({ session, token }) => {
// session callback is called whenever a session for that particular user is checked
console.log("user in ...next auth api", token);
session.user = token.user;
// since I get error, I return Promise.resolve(session)
return Promise.resolve(session);
},
},
});
then I write a middleware to get the session:
const isAuthenticatedUser = asyncErrors(async (req, res, next) => {
// when user logged in we create the session of user
// getSession gets auth-token from req.headers
const session = await getSession({ req });
// THIS RETURNS NULL
console.log("session", session);
if (!session) {
return next(new ErrorHandler("Login first to access this resource", 401));
}
req.user = session.user;
next();
});
export { isAuthenticatedUser };
I write api/me to check the user
import nc from "next-connect";
import connectToDb from "#/config/dbConnection";
import { currentUserProfile } from "#/controllers/authController";
import { isAuthenticatedUser } from "#/middlewares/auth";
import onError from "#/middlewares/errors";
const handler = nc({ onError });
connectToDb();
handler.use(isAuthenticatedUser).get(currentUserProfile);
export default handler;
When I visit http://localhost:3000/api/me, since session is logged as null, I get not authneticated, even though, in chrome debugging tools' Aplication tab next-auth credentials are present
Since https://next-auth.js.org/getting-started/upgrade-v4
Version 4 makes using the SessionProvider mandatory.
this is _app.js component
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
// `session` comes from `getServerSideProps` or `getInitialProps`.
// Avoids flickering/session loading on first load.
<SessionProvider session={session} refetchInterval={5 * 60}>
<Component {...pageProps} />
</SessionProvider>
)
}

This problem is happening due to not specifying e.preventDefault() on login button.
The working code should look like this :-
async function login(e) {
e.preventDefault(); //Add this to your code.
const getLoginStatus = await signIn("credentials", {
redirect: false,
username,
password,
})};

Related

SvelteKit Server Side Authentication with Supabase not creating cookie with Token

I'm trying to implement Server Side Authentication with Supabase and Sveltekit. I followed the Quickstart Guide and was able to do authentication client-side. (preventDefault on the submit event and do client-side POST request).
But when trying to do the same thing Server-Side , the auth cookie with the token is not created. Here's the logic:
// src/routes/login/+page.server.ts
import type { PostgrestResponse } from '#supabase/supabase-js';
import { supabaseClient } from '$lib/supabaseClient';
import type { Database } from '$lib/types/database.types';
import type { PageLoad } from './$types';
import type { PageServerLoad, Actions } from './$types';
import { redirect } from '#sveltejs/kit';
export const actions: Actions = {
'login-with-password': async ({ request }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');
console.log(email, password);
const { data, error } = await supabaseClient.auth.signInWithPassword({ email, password });
console.log(data);
if (error) {
return {
status: 500,
body: {
error: error.message
}
};
}
throw redirect(302, '/');
return { success: true };
}
};
data seems to hold the correct response, with token and everything, but that's not persisted as a cookie.
https://stackblitz.com/~/github.com/gkatsanos/client

Getting cors error in express and react app while redirecting when I use passport-local strategy and not when I use passport-google-oauth20

This is my back-end code.
import { config } from "dotenv"
import express,{Request,Response} from "express"
import mongoose, {ConnectOptions, Error} from "mongoose";
import DeckModal from './models/deck';
import cors from "cors"
import session from "express-session";
import passport from "passport";
import User from "./models/user";
import bcrypt from "bcryptjs"
import { UserDetails } from "./types/UserDetails";
import passportLocal from "passport-local"
const LocalStrategy=passportLocal.Strategy
const MongoDBStore = require('connect-mongodb-session')(session);
const GoogleStrategy =require("passport-google-oauth20")
config()
let PORT:number=5000
const app = express()
app.use(express.json())
const store = new MongoDBStore({
uri: process.env.MONGO_URI!,
collection: 'mysessions'
});
store.on('error', function(error:Error) {
console.log(error);
});
app.use(cors({ origin:"http://localhost:5173" ,credentials:true}))
app.set('trust proxy', 1)
// create session
app.use(session({
secret:process.env.SESSION_SECRET!,
resave:true,
saveUninitialized:true,
store:store,
// cookie:{
// httpOnly:true, //An HttpOnly Cookie is a tag added to a browser cookie that prevents client-side scripts from accessing data
// sameSite:"none", // If your frontend and backend is hosted on different origin then use sameSite:none in order to share cookie.
// secure:true, // it allows only https requests
// maxAge: 1000 * 60 * 60 * 24 * 7
// }
}))
app.use(passport.initialize())
app.use(passport.session())
// local strategy
passport.use(new LocalStrategy(
{usernameField:"email"},
function(username, password, done) {
User.findOne({ email: username }, function (err:Error, user:UserDetails) {
if (err) { return done(err); } // If there is error we will return an error to done function.
if (!user) { return done(null, false); } // If we don't find any user then we will return null and as there is no error we will send false
bcrypt.compare(password,user.password!,(err,result)=>{
if(err) throw err
if(result===true){
return done(null,user)
}
else{
return done(null,false)
}
})
});
}
));
//gooogle strategy
passport.use(
new GoogleStrategy(
{
callbackURL: "/auth/google/callback",
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
async (accessToken:any, refreshToken:any, profile:any, done:any) => {
const userInfo:UserDetails={
email:profile.emails[0].value,
profile_picture:profile.photos[0].value,
username:profile.displayName
}
try{
const user= await User.findOne({ email: profile.emails[0].value })
if(!user){
const newUser= new User(userInfo)
await newUser.save()
return done(null,newUser)
}
return done(null,user)
}
catch(err){
return done(null,false,err)
}
}
)
);
passport.serializeUser((user:any,done)=>{
return done(null,user._id)
})
passport.deserializeUser((id:string,done)=>{
User.findById(id,(err:Error,doc:any)=>{
const userInfo={
username:doc.username,
email:doc.email,
profile_picture:doc.profile_picture,
}
return done(null,userInfo)
})
})
// This is the recomended way to connect to the mongodb database using mongoose. source: https://docs.cyclic.sh/how-to/using-mongo-db#connection-example-mongoclient
mongoose.connect(process.env.MONGO_URI!,{
useUnifiedTopology: true,
useNewUrlParser: true
} as ConnectOptions )
.then(()=>{console.log("Connected to mongodb successfully");
// We will start to listen for request once the DB is connected
app.listen(process.env.PORT || PORT,()=>{
console.log("server is running on port "+PORT)
})
})
.catch((err:Error)=>{console.log(err)});
//Google auth
app.get('/auth/google', passport.authenticate('google', {
scope: ['profile','email']
// scope:['openid','profile', 'email']
}));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: "http://localhost:5173/login", failureMessage: true,successRedirect:"http://localhost:5173/auth/success"}));
app.get("/getuser",(req,res)=>{
if(req.user){
res.json(req.user) // here we get user in req.user because passports deserialize function attaches it automatically
}
else{
res.json(null)
}
})
app.post('/auth/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.send('done');
});
});
// Local auth
app.post("/signup",async(req:Request,res:Response)=>{
const {username,email,password}=req?.body
if(!username && !password && !email){
res.send("Please provide name, email and password")
return
}
try{
const user:UserDetails | null=await User.findOne({username})
if(!user){
const hashedPassword=await bcrypt.hash(password,10)
const newUser= new User({
username,
email,
password:hashedPassword
})
await newUser.save()
res.send("success")
}
else{
res.send("User already exist")
}
}
catch(error){
console.log(error)
}
})
app.post('/login',
passport.authenticate('local'),
(req, res) => {
if(req.user){
res.redirect("http://localhost:5173/auth/success")
}
else{
res.status(200).json({success:false});
}
}
);
I tried redirecting using successRedirect but that is also giving me same error
app.post('/login',
passport.authenticate('local',{successRedirect:"http://localhost:5173/auth/success"}));
**
When I authenticate using google auth it successfully redirects me but when I use local startegy for authentication it gives me cors error.**
Error message:
Access to XMLHttpRequest at 'http://localhost:5173/' (redirected from 'http://localhost:5000/login') from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
The front-tend request looks like this-
for local strategy:
const handleLogin = async (e:FormEvent<HTMLFormElement>) => {
e.preventDefault()
const data = { email, password };
axios.post("http://localhost:5000/login",data,{withCredentials:true})
}
for google strategy:
const handleGoogleLogin = async () => {
window.open("http://localhost:5000/auth/google","_self")
}

How to make simple protected route using nextAuth?

I wanna make simple protected route.
I have credentials provider and nextAuth middleware. I just wanna make simple logic:
if user is logged in he can visit /profile, and if he visits /signup or /signin redirect him to /profile, and if he isnt logged he cant visit /profile and redirect him to /signin
some routes are neutral - for example he can visit /shop while being logged in or not.
there is my [...nextauth].ts
export default NextAuth({
session: {
strategy: 'jwt',
},
providers: [
CredentialsProvider({
type: 'credentials',
async authorize(credentails) {
const { password, email } = credentails as Signin
try {
const client = await connectToDatabase()
if (!client) return
const db = client.db()
const user = await existingUser(email, db)
if (!user) throw new Error('Invalid credentails!')
const isPasswordCorrect = await verifyPassword(password, user.password)
if (!isPasswordCorrect) throw new Error('Invalid credentails!')
return { email: user.email, name: user.name, id: user._id.toString() }
} catch (e: unknown) {
if (e instanceof Error) {
throw new Error(e.message)
}
}
},
}),
],
})
Apart from other answers what you can do is-
At component mount at signin and sign up check user is authenticated or not. If authenticated. use router.push to profile else be at signin/signup.
At profile again check for authentiction at component mount, if not auth push to signin else be at profile. Important thing here is don't show the layout, content of profile page before checking user is authenticated or not. Use a spiner or loader till auth check is going on.
write a middleware
const authorizedRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
// write logic to handle errors
new ErrorHandler(
`Role (${req.user.role}) is not allowed`,
403
)
);
}
next();
};
};
then whichever routes you want to protect, use this middleware. Then on protected pages' getServerSideProps
export async function getServerSideProps(context) {
const session = await getSession({ req: context.req });
if (!session || session.user.role !== "admin") {
return {
redirect: {
destination: "/home",
// permanent - if `true` will use the 308 status code which instructs clients/search engines to cache the redirect forever.
permanent: false,
},
};
}
return {
props: {},
};
}

Is there a way to use ctx.session.$create in api using Blitz.js

I am trying to use blitz.js login API in a Flutter project. So I have created a /api/auth/login.ts file with the following code
import { getAntiCSRFToken, getSession, SecurePassword } from "#blitzjs/auth"
import { authenticateUser } from "app/auth/mutations/login"
import { AuthenticationError } from "blitz"
import db from "db"
import { Role } from "types"
const handler = async (req, res) => {
const session = await getSession(req, res)
const { email, password } = req.body
if (req.method !== "POST" || !req.body.data || !session.userId) {
res.status(401).json({ error: `Do not tamper with this route!` })
} else {
console.log("Create a new session for the user")
// Create a new session for the user
//login
const user = await authenticateUser(email, password)
const user = await db.user.findFirst({ where: { email } })
if (!user) return res.json({ data: "Hello", email, password })
const result = await SecurePassword.verify(user.hashedPassword, password)
const { hashedPassword, ...rest } = user
await req.session.$create({ userId: user.id, role: user.role as Role })
res.json({ rest })
}
export default handler
I also tried to use their docs but it was not clear enough and understandable
Can I use ctx.session.$create and insert it to db using blitz.js api
I have solved the problem using this code
import { Role } from "types"
import { authenticateUser } from "app/auth/mutations/login"
import { getSession } from "#blitzjs/auth"
export default async function customRoute(req, res) {
const session = await getSession(req, res)
const { email, password } = req.body
console.log(email, password)
console.log(session.$isAuthorized())
const user = await authenticateUser(email, password)
if (user.id === session.userId) {
return res.status(409).json({ error: `Already exist` })
}
await session.$create({ userId: user.id, role: user.role as Role })
// // res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ userId: session.userId }))
}
At first, I was getting a CSRF mismatch error and then a localStorage is undefined and now somehow everything is working with this code.

Cannot get the additional detail i.e account id from session callback using Next_auth

I want to extract account id as additional detail from session callback using Next_auth. Whatevere I add in jwt token and pass it to session callback still it returns the same data. I tried logging out and loggin in many times but all in vain. Any help will be appreciated.
Code in pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google";
import FacebookProvider from "next-auth/providers/facebook";
export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code"
}
}
}),
FacebookProvider({
clientId: process.env.FACEBOOK_CLIENT_ID,
clientSecret: process.env.FACEBOOK_CLIENT_SECRET
})
],
secret: process.env.SECRET,
session:{
strategy:"jwt"
},
callback: {
async jwt({ token, account, user }) {
// Persist the OAuth access_token to the token right after signin
if (user) {
token.id = account.id;
}
return token
},
async session({ session, token }) {
// Send properties to the client, like an access_token from a provider.
session.user.id = token.id;
return session;
},
// redirect: async(url, _baseUrl) => {
// if (url === "/profile") {
// return Promise.resolve("/");
// }
// return Promise.resolve("/");
// }
}
})
Code in login page
import { useSession, getSession, signIn } from "next-auth/react"
useEffect(() => {
const good = async () => {
const session = await getSession();
if (session) {
console.log(session);
}
}
good();
}, [])
Result I get is
{"user":{"name":"myname","email":"myemail","image":"imageUrl"},"expires":"2022-09-05T11:28:41.840Z"}

Resources