I'm trying to log in a user using Passport Js (Google Auth), I configured Passport Js and i'm getting the user profile in my console, but I didn't know how to show the user profile in frontend, the configure for PassportJs goes like:
passport.use(
new GoogleStrategy(
{
clientID: "",
clientSecret: "",
callbackURL: "/auth/google/callback",
scope: ["profile", "email"],
},
async (accessToken, refreshToken, profile, done) => {
// find current user in UserModel
const currentUser = await User.findOne({
googleId: profile.id
});
// create new user if the database doesn't have this user
if (!currentUser) {
const newUser = await new User({
googleId: profile.id,
email: profile.emails[0].value,
displayName: profile.displayName,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
profilePic: profile.photos[0].value,
}).save();
if (newUser) {
done(null, newUser);
}
}
console.log("CURRNT USER: ", currentUser);
done(null, currentUser);
}
)
);
// serialize the user.id to save in the cookie session
// so the browser will remember the user when login
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id)
.then(user => {
done(null, user);
})
.catch(e => {
done(new Error("Failed to deserialize an user"));
});
});
And in the Auth.js route:
// when login is successful, retrieve user info
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
error: false,
message: "succesfull",
user: req.user,
cookies: req.cookies
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});
// auth with google
router.get("/google", passport.authenticate("google", ["profile", "email"]))
// redirect to home page after successfully login via google
router.get(
"/auth/google/callback",
passport.authenticate("google", {
successRedirect: "http://localhost:3000/",
failureRedirect: "/login/failed"
})
);
I'm using Context to let the app knows if the user is logged in or not.
**Login.jsx: Normal Logging Using express and Mongodb **
const handleSubmit = async (e) => {
e.preventDefault();
dispatch({ type: "LOGIN_START" });
try {
const res = await axios.post("/login", {
email: userRef.current.value,
password: passwordRef.current.value,
});
dispatch({ type: "LOGIN_SUCCESS", payload: res.data });
} catch (err) {
dispatch({ type: "LOGIN_FAILURE" });
setError(true)
}
};
//Now I tried this code to log in a user using Google Auth but it didn't work
useEffect(() => {
fetch(`http://localhost:4000/login/success`, {
method: 'GET',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Credentials': true,
},
})
dispatch({ type: "LOGIN_START" })
.then((response) => {
if (response.status === 200) return response.json();
throw new Error('failed to authenticate user');
})
.then((responseJson) => {
dispatch({ type: "LOGIN_SUCCESS", payload: responseJson.data });
})
.catch((error) => {
dispatch({ type: "LOGIN_FAILURE" });
console.error("Failed to authenticate user", error)
});
}, []);
const google = () => {
window.open("http://localhost:4000/auth/google/callback", "_self");
};
The full code is here: https://codesandbox.io/s/nervous-mountain-e5t9d4?file=/api/routes/auth.js
let me share with you the perfect code.
passportStratergy.js
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('../model/user');
const { USER_TYPES } = require('../constants/authConstant');
const googlePassportStrategy = passport => {
passport.serializeUser(function (user, cb) {
cb(null, user);
});
passport.deserializeUser(function (user, cb) {
cb(null, user);
});
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENTID,
clientSecret: process.env.GOOGLE_CLIENTSECRET,
callbackURL: process.env.GOOGLE_CALLBACKURL
}, async function (accessToken, refreshToken, profile, done) {
if (profile){
let userObj = {
'username':profile.displayName,
'ssoAuth': { 'googleId': profile.id },
'email': profile.emails !== undefined ? profile.emails[0].value : '',
'password':'',
'userType':USER_TYPES.User
};
let found = await User.findOne(User,{ 'email': userObj.email });
if (found) {
const id = found.id;
await User.updateOne(User, { _id :id }, userObj);
}
else {
await User.create(User, userObj);
}
let user = await User.findOne(User,{ 'ssoAuth.googleId':profile.id });
return done(null, user);
}
return done(null, null);
}
));
};
module.exports = { googlePassportStrategy };
auth.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const { socialLogin } = require('../services/auth');
router.get('/auth/google/error', (req, res) => {
res.loginFailed({ message:'Login Failed' });
});
router.get('/auth/google',passport.authenticate('google', {
scope: ['profile', 'email'],
session:false
}));
router.get('/auth/google/callback',
(req,res,next)=>{
passport.authenticate('google', { failureRedirect: '/auth/google/error' }, async (error, user , info) => {
if (error){
return res.send({ message:error.message });
}
if (user){
try {
//let result = await socialLogin(user.email);
// here your business logic for login user.
return res.send({
data: result.data,
message:'Login Successful'
});
} catch (error) {
return res.send({ message: error.message });
}
}
})(req,res,next);
});
module.exports = router;
index.js
const passport = require('passport');
const { googlePassportStrategy } = require('./passportStrategy');
googlePassportStrategy(passport);
Related
I've followed a guide on Medium on the implementation of Passport js with a MERN stack and whilst I got authentication to work, I'm struggling to persist users between routes. Below are snippets of my code:
Backend
Server.js setup (part of it):
const mongoose = require('mongoose')
const MongoStore = require('connect-mongo')
const passport = require('./passport/setup')
const MONGO_URI = 'db'
const app = express();
mongoose
.connect(MONGO_URI, { useNewUrlParser: true })
.then(console.log(`MongoDB connected ${MONGO_URI}`))
.catch(err => console.log(err))
app.set("")
const PORT = process.env.PORT || 5000;
if (PORT !== 5000) {
app.use(enforce.HTTPS({ trustProtoHeader: true }));
}
app.use(cors({
origin: true,
credentials: true,
}))
app.options('*', cors())
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: MONGO_URI })
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
Passport.js Local Strategy setup:
const bcrypt = require('bcryptjs');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User')
passport.serializeUser((user, done) => {
done(null, user.id);
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(new LocalStrategy({ usernameField: "email" }, (email, password, done) => {
User.findOne({ email: email })
.then(user => {
if (!user) {
return done(null, false, { message: 'No User Found' })
} else {
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: "Wrong Password" });
}
});
}
})
.catch(err => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
Login route:
router.post("/new-login", async (req, res, next) => {
passport.authenticate("local", function (err, user, info) {
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function (err) {
if (err) {
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
})
Frontend React:
Posting Login Data:
export const postData = async (url, data) => {
try {
console.log('posting this ', data)
const config = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'backend',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}
}
const response = await axios.post(url, data, config);
console.log('getting this', response)
return {
data: response.data,
error: "",
success: true,
};
} catch (exp) {
console.log('Error', exp)
return {
success: false,
error: exp.response.data,
};
}
};
Axios call:
let response = await postData('/login/new-login', { email, password })
When trying to login all of this works and returns the login success message with user.id from the login route, but when I look at the Axios response there's no reference to any user or session as shown below:
I'm probably not understanding how users are passed to React from the backend but shouldn't res.user exist here following req.LogIn in Passport? Or should I take the user.id and save it in a global React state and attach it every time I do a request to a protected route? Right now if I do a GET request to a protected route I get a req.user undefined message.
Frontend Axios call:
export const getData = async (url) => {
console.log('not called?')
try {
const config = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'backend',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}
}
const response = await axios.get(url, config);
console.log('response? ', response)
return {
data: response.data,
error: "",
success: true,
};
} catch (exp) {
return {
success: false,
error: exp.response.data,
}
}
}
Backend Protected Route:
router.get("/new-protected", passport.authorize('local', { failureRedirect: '/login' }), (req, res) => {
console.log('whats request?', req.user)
res.send(`Hello, logged in`);
})
When trying this I remove the middleware to see what req looks like, but I always get undefined req.user, so I'm guessing either I'm supposed to be passing req.user somehow in my axios call or I'm not retrieving a user when logging in initially, any ideas on what I'm doing wrong?
Many thanks
You don't have to do anything about the user's data once you log in with authorization, the passport middleware is here for the job, however we should at least set up the right condition for them to exchange the credential, try to change the setting a bit to see how it go :
this part :
app.use(cors({
origin: //put the EXACT URL you host from Vercel
method: 'GET,PUT,POST,DELETE'
credentials: true,
}))
app.set('trust proxy', 1)
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: MONGO_URI })
})
);
app.use(express.json()); // optional: you can uninstall bodyParser since express have now his own module so you can just replace with this line.
app.use(passport.initialize());
app.use(passport.session());
and update a bit this part at the frontend Axios call :
const config = {
withCredentials: true,
};
I don't know if you have already the issue in localhost or after deployment, if it sill not working than we may have to fix on the cookie session.
I'm trying to use Google OAuth in my App, on the Log In page and the Sign Up page, I'm looking for the best way and the easiest! I tried Passport Js, but I'm stuck right now.
I'm using Mongoose right now and I'm signing up and in users perfectly, but now i want to add a feature where the user can sign in using his google account, I'm looking for a way where the app can get the Email the user is using for his google account and then look if the email is already registered if so redirect him to the home page and if not sign his email up, save it to database, and then redirect to the home page.
This is how my Auth.js looks like
//REGISTER
router.post("/register", async (req, res) => {
try {
//generate new password
const salt = await bcrypt.genSalt(10);
const hashedPass = await bcrypt.hash(req.body.password, salt);
//create new user
const newUser = new User ({
username: req.body.username,
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: hashedPass,
repeatPassword: hashedPass,
birthday: req.body.birthday,
});
//save user and respond
const user = await newUser.save();
res.status(200).json(user);
} catch (err) {
res.status(500).json(err);
}
});
//LOGIN
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
// if(!user) return res.status(400).json("Wrong credentials!");
!user && res.status(400).json("Wrong credentials!");
const validated = await bcrypt.compare(req.body.password, user.password);
// if(!validated) return res.status(400).json("Wrong credentials!");
!validated && res.status(400).json("Wrong credentials!");
const { password, ...others } = user._doc;
return res.status(200).json(others);
} catch (err) {
return res.status(500).json(err);
}
});
PassportJs configuration I used: but didn't work
passport.use(
new GoogleStrategy(
{
clientID: "MyClientId",
clientSecret: "Myclientsecret",
callbackURL: "/api/auth/google/callback",
},
function (accessToken, refreshToken, profile, done) {
User.find(
{
social: profile.provider,
social_id: profile.id,
},
(err, user) => {
if (user.length === 0) {
const user = new User({
email: profile.email,
username: profile.displayName,
profilePic: profile.photos[0],
social: profile.provider,
social_id: profile.id,
});
const userModel = new User(data);
userModel.save();
done(null, profile);
}
if (err) {
return done(err);
}
},
);
return done(null, profile);
}
)
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
success: true,
message: "successfull",
user: req.user,
// cookies: req.cookies
});
}
});
router.get("/login/failed", (req, res) => {
res.status(401).json({
success: false,
message: "failure",
});
});
router.get("/google", passport.authenticate("google", { scope: ["profile"] }));
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: CLIENT_URL,
failureRedirect: "/login/failed",
})
);
How to return API errors to Login Component in NextAuth.js.Actually, I am trying to pass the Errors back to Login Component in NextAuth(Credentials Provider). I am getting this object in console error: "CredentialsSignin" ok: false status: 401 url: null [[Prototype]]: Object Everything is working fine like I am able to log in, but when I am trying to handle errors coming from APIs, I am unable to handle them.
[...nextauth.js] File
export default (req, res) =>
NextAuth(req, res, {
providers: [
CredentialsProvider({
authorize: async (credentials) => {
try {
const data = {
email: credentials.email,
password: credentials.password
}
const user = await login(data);
console.log("401 Error",user.data);
if (user.data.status==200) {
console.log("200 data",user.data);
return Promise.resolve(user.data);
}else if(user.data.status==401){
// Here I wants to Handle Errors and return them back to My login Compenent
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
pages: {
signIn: '/login',
},
callbacks: {
jwt: async ({token,user})=>{
if(user){
token.userid = user.id;
token.name = user.username;
token.token = user.token;
}
return token;
},
session: (session,token)=>{
return session;
}
},
secret:"test",
jwt:{
secret:"test",
encryption:true,
},
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
jwt: true,
maxAge: 1 * 3 * 60 * 60, // 3 hrs
updateAge: 24 * 60 * 60, // 24 hours
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = 'api/auth/login';
const result = await axios.post(url,data,config);
return result;
};
Login Components
const LoginSubmit = async (event) => {
event.preventDefault();
const enteredEmail = inputText.email;
const enteredPassword = inputText.password;
// console.log(enteredEmail);
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("Final Result",result);
};
Yes you can do it :
Try :
// component.js
const res = await signIn("credentials", {
email: inputs.email,
password: inputs.password,
callbackUrl: `/`,
redirect: false,
});
if (res?.error) {
setLoading(false);
Swal.fire("Invalid Login", "username or password is incorrect");
}
and in Nextauth file you must throw an error if a user entred invalid credential!
//[...nextauth].js
const providers = [
Providers.Credentials({
name: "credentials",
authorize: async (credentials) => {
//statr try
try {
const user = await
axios.post(`${API_URL}/auth/local`, {
identifier: credentials.email,
password: credentials.password,
});
if (user) {
return { status: "success",
data:user.data };
}
} catch (e) {
throw new Error("Not found ");
}
},
}),
];
I am using React and Express. Only the React is hosted on heroku and I am hosting the Express server locally. With Express I am using Passportjs with MongoDB.
The problem is that React clien t works well on local deployment, however once I deploy my React App on Heroku it does not work well propertly. When using my deployed React app, I can register a user but I cannot log in a user. It does not return error and POST("/login") returns "Successfully Authenticated" back to me and when I try to access req.user using axios it returns nothing back when it should be returning the user.
React Code
const getUser = () => {
axios({
method: "GET",
withCredentials: true,
url: "http://localhost:3001/user",
})
.then((res) => {
{
console.log(res.data);
if (res.data.username != null)
setMessage("Welcome " + res.data.username);
}
})
.catch((err) => console.log(err));
};
const loginTheUser = async () => {
await axios({
method: "POST",
data: {
username: username,
password: password,
},
withCredentials: true,
url: "http://localhost:3001/login",
}).then((res) => {
if (res.data === "Successfully Authenticated") {
history.push("/");
}
console.log(res.data);
});
await getUser();
};
Express code
// Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(
cors({
origin: [
"http://localhost:3000",
"https://my-herokuapp.com",
], // <-- location of the react app were connecting to
credentials: true,
})
);
app.use(
session({
secret: "secretcode",
resave: true,
saveUninitialized: true,
})
);
app.use(cookieParser("secretcode"));
app.use(passport.initialize());
app.use(passport.session());
require("./passportConfig")(passport);
//Login
app.post("/login", (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) throw err;
if (!user) {
console.log("Unsuccessful login");
res.send("Unsuccessful login");
} else {
req.logIn(user, (err) => {
if (err) throw err;
{
console.log("Login success");
res.send("Successfully Authenticated");
}
});
}
})(req, res, next);
});
//Register
app.post("/register", (req, res) => {
User.findOne({ username: req.body.username }, async (err, doc) => {
if (err) throw err;
if (doc) res.send("User Already Exists");
if (!doc) {
const hashedPassword = await bcrypt.hash(req.body.password, 10);
const newUser = new User({
username: req.body.username,
password: hashedPassword,
});
await newUser.save();
res.send("User Created");
}
});
});
//get User
app.get("/user", (req, res) => {
res.send(req.user); // The req.user stores the entire user that has been authenticated inside of it.
});
Passport config
const User = require("./user");
const bcrypt = require("bcryptjs");
const localStrategy = require("passport-local").Strategy;
module.exports = function (passport) {
passport.use(
new localStrategy((username, password, done) => {
User.findOne({ username: username }, (err, user) => {
if (err) throw err;
if (!user) return done(null, false);
bcrypt.compare(password, user.password, (err, result) => {
if (err) throw err;
if (result === true) {
return done(null, user);
} else {
return done(null, false);
}
});
});
})
);
passport.serializeUser((user, cb) => {
cb(null, user.id);
});
passport.deserializeUser((id, cb) => {
User.findOne({ _id: id }, (err, user) => {
const userInformation = {
username: user.username,
};
cb(err, userInformation);
});
});
};
I'm implementing login functionality to my application and am trying to return the response from a dispatched thunk action. I'm purposely entering the incorrect password because i'd like for the errors that are set in my backend to display within an antd notification on the frontend. My expected response should be:
return res.status(400).json({
success: false,
message: 'Invalid email address or password.',
});
but instead i'm getting the following in the console:
Error: Request failed with status code 400
Route:
const loginUser = async (req, res) => {
// Validate Login Input
const { error } = validateLoginInput(req.body);
if (error)
return res
.status(400)
.json({ success: false, message: error.details[0].message });
req.body.email = req.body.email.toLowerCase();
req.body = sanitize(req.body);
const { email, password } = req.body;
try {
// See if user exists
let user = await User.findOne({ email });
if (!user) {
return res.status(400).json({
success: false,
message: 'Invalid email address or password.',
});
}
// Compare passwords
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).json({
success: false,
message: 'Invalid email address or password.',
});
}
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
process.env.JWT_SECRET,
{ expiresIn: 3600 },
(error, token) => {
if (error) throw error;
res.json({ token });
}
);
} catch (error) {
res.status(500).json({ success: false, messsage: 'Server error' });
}
};
Actions:
export const loginBegin = () => ({
type: LOGIN_BEGIN,
});
export const loginSuccess = (user) => ({
type: LOGIN_SUCCESS,
payload: user,
});
export const loginFail = (error) => ({
type: LOGIN_FAIL,
payload: error,
});
Thunk:
export const attemptLogin = (formData) => async (dispatch) => {
dispatch(loginBegin());
return await postLogin(formData)
.then((res) => {
dispatch(loginSuccess(res.data));
console.log(res.data); <-- This doesn't even show in console
})
// .then(() => {
// setTimeout(() => {
// dispatch(push('/app'));
// }, 2000);
// })
.catch((error) => {
dispatch(loginFail(error));
});
};
onSubmit:
const onSubmit = async (values) => {
const { email, password } = values;
setLoading(true);
try {
const response = await dispatch(attemptLogin({ email, password }));
console.log(response); <-- returns undefined
if (response.data.success) {
setLoading(false);
notification['success']({
message: 'Congrats!',
description: response.message,
});
} else {
notification['error']({
message: 'Uh-oh!',
description: response.message,
});
}
} catch (error) {
console.log(error);
}
};
The problem is that you are not returning the response in the promise. Also, if you are getting a 400 response, it probably rejects the promise or throws an error.
export const attemptLogin = (formData) => async (dispatch) => {
dispatch(loginBegin());
return await postLogin(formData)
.then((res) => {
dispatch(loginSuccess(res.data));
return res;
})
.catch((error) => {
dispatch(loginFail(error));
return error; // this will only work if the error is the response object.
});
};