I am new to using Passport authentication with express between my
server and client (on two different ports), and am having great
trouble understanding both the concept of maintaining session state
with Passport and also trying to retrieve req.session.passport.user
in order to determine if a request is sent from a user that has
successfully logged on.
Checking Persistent Session State with Passport
In my code, I have a route to my profile page that uses my own
authenticated function. My login route uses
passport.authenticate('local'), however. Should I be applying this
to ALL subsequent routes (like for my profile), or is my separate
authentication expected?
req.session.passport undefined
For some reason, the cookie that is sent with the /profile GET request is missing the passport object after a successful login (by
passport.use('local-login')) It looks like the following if I
console.log(req.session) in authenticated. Why is this?
Session {
cookie:
{ path: '/',
_expires: 2017-07-11T01:38:30.087Z,
originalMaxAge: 14400000,
httpOnly: true,
secure: false } }
Server Code
// server.js
let app = express();
app.use(cors());
import passportConfig from '../config/passport';
passportConfig(passport); //apply passport configuration
var jsonParser = bodyParser.json();
app.use(jsonParser);
app.use(cookieParser('mySecret'));
app.use(session({
secret: 'mySecret',
resave: false,
saveUninitialized: true,
cookie: {secure: false, maxAge: 4*60*60*1000 }
}));
app.use(passport.initialize());
app.use(passport.session());
function authenticated (req, res, next) {
// req.session.passport is undefined
if (req.session.passport.user) {
next();
}
else {
res.redirect('/signup');
}
}
app.get('/profile', authenticated, (req, res) => {
// server code handling route
}
app.post('/login', passport.authenticate('local-login', {
session: true
}), (req, res) => {
res.json({
email: req.user.local.email
});
});
// passport.js
import User from '../models/User';
export default function passportConfig(passport) {
passport.serializeUser((user, done) => {
console.log('serializing user');
done(null, user.id);
});
// used to deserialize the user
passport.deserializeUser((id, done) => {
console.log('deserializing user');
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use('local-login', new LocalStrategy({
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
}, (req, email, password, done) => {
// if verification is successful
var newUser = new User();
// set credentials of newUser...
return done(null, newUser);
}));
}
Client Code
// profile.js (client action code with react-redux)
export function profile(terms, location) {
return (dispatch) => {
return fetch('profile',
{credentials: 'include'})
.then(response => response.json())
.then(json => { dispatch(profileSuccess(json)); })
.catch(error => dispatch(profileError(error)));
}
}
I am aware there are actually quite a lot of articles/posts available
of similar issues, but applying many of the different solutions have
all failed for me. Any help to my problem specifically is greatly
appreciated.
Related
I have recently deployed an app on Heroku. It works fine in the dev environment and it works fine when I access it through my PC. I cannot log into the app on any device that is not my development PC. I can post to MongoDB Atlas on any device no problem, so the database looks like it is connected and able to receive information. I looked at another answer for a similar problem from a different user and the reply was
"After looking at your repo, I would double check your database and API calls. In your client, you are fetching data from a localhost URL. If you are running React client side, the API call will try to fetch information locally on your device instead of from the Heroku server."
I don't know what the fix would be if there is something wrong with my calls. The MongoDB Atlas Database is recording the registration information no problem.
this is the github link:
https://github.com/chrishjung/Armada-Development-master
this is it on Heroku:
https://armada-phr.herokuapp.com/
UPDATE:
So somehow for a short period of time, I was able to register users and login after I commented out "secure: true", but now it does not work again.
snippets from app
// Init Session
app.use(
session({
secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: true,
proxy: true, // add this line
cookie: {
//secure: true, It worked for a short time when I commented this out
httpOnly: true,
},
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);
// For Passport JS Authentication
app.use(passport.initialize());
app.use(passport.session());
require("./utils/passport.auth");
app.use((req, res, next) => {
res.locals.user = req.user;
next();
});
//passport.auth:
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user.model');
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
const user = await User.findOne({ email });
// Username/email does NOT exist
if (!user) {
return done(null, false, {
message: 'Username/email not registered',
});
}
// Email exist and now we need to verify the password
const isMatch = await user.isValidPassword(password);
return isMatch
? done(null, user)
: done(null, false, { message: 'Incorrect password' });
} catch (error) {
done(error);
}
}
)
);
passport.serializeUser(function (user, done) {
done(null, user.id);
});
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
//auth.route.js
router.get(
"/login",
ensureLoggedOut({ redirectTo: "/" }),
async (req, res, next) => {
try {
return res.render("login");
} catch (e) {
console.log("e", e);
}
}
);
router.post(
"/login",
passport.authenticate("local", {
failureRedirect: "/auth/login",
failureFlash: true,
}),
async (req, res) => {
try {
res.redirect(`/`);
} catch (e) {
console.log("e", e);
}
}
);```
After successfully deploying your app to heroku you get a url link to your app ..
You find this url in your app settings in heroku.
After finding it , Copy that url and use it by replacing the "http://localhost:××××" by the new url
My PassportJS setup doing something weird, I can see req.user after logged-in in the deserialize function but after all req.isAuthenticated() false and no req.user found. I have already referred a bunch of questions regarding this issue on StackOverflow, almost every question on StackOverflow. Nothing works for me, not sure what is the case here. I'm posing my code, can someone pls tell me what is wrong here and why it's happening. At least a fix! :(
I'm using passport-google-oauth as my strategy. Also, the client is an Angular app.
I also tried with passport-google-oauth20 by accessing directly.
What I found is, in social_logins.google_callback the req.user can
be found and also req.isAuthenticated() returns true. After the
redirect happens it won't work.
Thanks in advance!
// app.js
const pgSession = require('connect-pg-simple')(session);
app.set('trust proxy', 1);
/** #type {session.CookieOptions} */
const cookieOptions = {
path: '/',
secure: false,
httpOnly: false,
maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
};
app.use(session({
secret: require('./config').session.secret, // session secret
cookie: cookieOptions,
proxy: true,
name: config.session.name,
resave: false,
saveUninitialized: true,
store: new pgSession({
pool: db.pool,
tableName: 'sess'
}),
}));
require('./config/passport')(passport);
app.use(passport.initialize());
app.use(passport.session());
// passport.js
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser(async (user, done) => {
done(null, user);
});
// passport login.js
app.get('/social-logins/connect/google', passport.authenticate('google-login', {
successRedirect: '/social-logins/google',
failureRedirect: '/social-logins/google',
scope: ['profile', 'email'],
failureFlash: true,
}));
// routes/index.js
router.get('/social-logins/google', social_logins.google_callback);
// callback funtion
social_logins.google_callback = async (req, res) => {
try {
const { user } = req;
if (!user) return res.redirect('https://localhost:3000/auth/login?message=Login Failed');
const url = `https://localhost:3000/auth/dashboard`;
req.session.save(() => {
return res.redirect(url);
});
} catch (error) {
log_error(error);
res.redirect(`https://localhost:3000/auth/login?error=true`);
}
};
// passport strategy
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
module.exports = new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: 'https://localhost:3000/social-logins/connect/google',
passReqToCallback: true
}, async (req, accessToken, refreshToken, profile, done) => {
try {
const acc = {}; // find from db and assign db.query('select * from users where id = $1', [profile.id]);
return done(false, acc, { message: 'User successfully logged in' });
} catch (error) {
return done(error);
}
});
I resolved this by setting domain to cookieOptions.
I'm working on integrating Auth0 into a MERN Stack app. The flow should look like this:
User clicks the login button which triggers Auth0Lock.show()
User fills in their credentials and clicks the submit button
The callback URL of the API is hit which logs the user in and redirects them back to the front-end app
(everything looks like it's working fine up to this point)
The front-end requests user information from the API
The front-end receives the information and redirects
This seems to be a fairly standard authentication flow. The problem is that when the front-end asks the back-end for user information, there's an error:
UnauthorizedError: No authorization token was found
My setup looks essentially like this:
// client-side config
const lock = new Auth0Lock(clientID, domain, {
auth: {
responseType: 'token',
audience: 'https://${domain}/userinfo',
redirectUrl: API_URL + '/api/users/callback',
params: {
scope: 'openid profile email' // no change
}
}
})
// server.js
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// [DB setup]
var sessConfig = {
secret: "[random string]",
cookie: {
sameSite: false
},
resave: false,
saveUninitialized: true
};
if(app.get('env') === 'production') sessConfig.cookie.secure = true;
app.use(session(sessConfig));
const {domain, clientID, clientSecret, callbackURL} = require('./config/auth0');
const passportStrategy = new Auth0Strategy(
{domain, clientID, clientSecret, callbackURL},
(accessToken, refreshToken, extraParams, profile, done) => done(null, profile)
)
passport.use(passportStrategy);
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
app.use(passport.initialize());
app.use(passport.session());
// [routing]
// routes/users.js
router.get('/callback', (req, res, next) => {
passport.authenticate('auth0', (err, user, info) => {
if(err) return next(err);
if(!user) return next(info);
req.logIn(user, err => {
if(err) return next(err);
const returnTo = req.session.returnTo;
delete req.session.returnTo;
res.redirect(returnTo || clientRootURL + '/callback');
})
})(req, res, next);
})
router.get(
'/current',
require('cors')(),
authenticate,
(req, res) => {
res.json({
id: req.user.id,
name: req.user.name,
email: req.user.email
});
}
);
// authenticate.js
module.exports = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${domain}/.well-known/jwks.json`
}),
audience: clientID,
issuer: `https://${domain}/`,
algorithms: ['RS256']
});
The vast majority of comes straight out of the Auth0 documentation.
I'm trying to get the user info from the /users/current endpoint after logging in and it says it can't find authorization. Does anyone have any idea how to get this to work?
You should be calling the /userinfo endpoint to get the user profile, or getting the info from the id_token. Take a look at this doc
https://auth0.com/docs/api/authentication#get-user-info
Every authenticated frontend call should contain:
headers: {
Authorization: `Bearer ${token}`,
},
where token should be:
const token = await getAccessTokenSilently();
getAccessTokenSilently is a public function of auth0 lib.
See: getAccessTokenSilently doc
I'm writing one of my first applications in NodeJS so please bear with me. I've managed to successfully authenticate a user to our Active directory and I can see the connect.sid cookie being set and used on the subsequent requests.
Upon debugging the application by dumping the req object I can also see that the user variable has been set successfully. From the documentation I've read that seems to be a criteria for a successful session match?
However, the request is still getting a 401 Unauthorized.
To summarize:
The user is successfully authenticated after posting credentials /login.
Upon successful authentication the user is redirected to "/".
The "/" path replies with 401 Unauthorized.
Any ideas much appreciated. Code below.
const express = require('express');
var bodyParser = require('body-parser');
var session = require('express-session');
var passport = require('passport')
var ActiveDirectoryStrategy = require('passport-activedirectory')
// Setup the authentication strategy
passport.use(new ActiveDirectoryStrategy({
integrated: false,
ldap: {
url: 'ldap://myad.company.com',
baseDN: 'DC=domain,DC=company,DC=com',
username: 'user',
password: 'password'
}
}, function (profile, ad, done) {
ad.isUserMemberOf(profile._json.dn, 'Group', function (err, isMember) {
if (err) return done(err)
return done(null, profile)
})
}));
passport.serializeUser(function(user, done) {
done(null, JSON.stringify(user));
});
passport.deserializeUser(function(user, done) {
done(null, JSON.parse(user));
});
const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(session(
{ secret: "password" }
));
app.use(passport.initialize());
app.use(passport.session());
// For debugging purposes
app.use(function (req, res, next) {
console.log(req)
next()
})
// The login page posts a form containing user and password
app.get("/login", (req, res) => {
res.sendFile(__dirname + '/public/index.html');
})
// Handler for the login page. Receives user and password and redirects the user to /
app.post('/login',
passport.authenticate('ActiveDirectory', {
failWithError: true,
successRedirect: "/",
failureRedirect: "/login"
}
), function(req, res) {
res.json(req.user)
}, function (err) {
res.status(401).send('Not Authenticated')
}
)
// This is where the issue happens. The page returns "Unauthorized".
// Using console.log(req) shows that the user property has been set to the req object.
// However, for some reason it still fails.
app.get('/',
passport.authenticate('ActiveDirectory', {
failWithError: true,
}
), function(req, res) {
res.send("test")
}, function (err) {
res.status(401).send('Not Authenticated')
})
Found what I did wrong!
The .authenticate method is only used to validate credentials, not to validate a session.
So this:
app.get('/',
passport.authenticate('ActiveDirectory', {
failWithError: true,
}
), function(req, res) {
res.send("test")
}, function (err) {
res.status(401).send('Not Authenticated')
})
Should become:
app.get('/', function(req, res, next) {
// This is verifying that the user part has been populated,
// which means that the user has been authenticated.
if (req.user) {
res.send('Returning with some text');
} else {
// If the user property does no exist, redirect to /login
res.redirect('/login');
}
});
Another thing that I changed was the serialize/deserialize functions:
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
This removes redundant serializing/deserializing.
These articles really helped me understand the flow:
http://toon.io/understanding-passportjs-authentication-flow/
https://www.airpair.com/express/posts/expressjs-and-passportjs-sessions-deep-dive
Hope it helps someone else!
/Patrik
I'm having an issue with Passport.js using the ActiveDirectory strategy where I'm successfully authenticating but fail to maintain a session on subsequent requests. After authentication, any call to req.isAuthenticated() returns false. Additionally, no cookie is being set on the client's browser. I've spent the day pouring over similar posts but so far none of the suggested solutions have worked.
Some observations to note:
-Authentication is always successful and returns the req.user to client.
-The console.log in serializeUser() always fires and correctly logs the user record.
-The console.log in deserializeUser() never fires, I'm not sure when this function is supposed to be called but it doesn't appear to be?
-After logging in, each time I make a call to my /test endpoint the non-authenticated condition fires.
// Passport Requires
const passport = require('passport');
const session = require('express-session');
var ActiveDirectoryStrategy = require('passport-activedirectory');
// Setup
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: false,
cookie: { secure: false, maxAge: 600000 }
}));
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function(user, done) {
console.log('userStrategy -- serialized:', user)
done(null, user);
});
passport.deserializeUser(function(user, done) {
console.log('userStrategy -- deserializeUser', user)
done(null, user);
});
passport.use(new ActiveDirectoryStrategy({
integrated: false,
passReqToCallback: true,
ldap: {
url: "url",
baseDN: "baseDN",
username: `username`,
password: `password`
}
}, function (req, profile, ad, done) {
ad.isUserMemberOf(profile._json.dn, 'Access Group', function (err, isMember) {
console.log('isMember:', isMember)
if (err) {
return done(err)
} else {
return done(null, profile)
}
})
}))
// Login Route
app.post('/login',
passport.authenticate('ActiveDirectory', { failWithError: true }),
function (req, res) {
console.log('Authenticated');
return res.status(200).send(req.user);
}, function (err) {
console.log('Not Authenticated');
return res.sendStatus(401).send(err);
}
)
// Test endpoint to check whether user is authenticated
app.get('/test', function(req, res) {
if (req.isAuthenticated()) {
res.send('Youre authenticated!')
} else {
res.send('Youre not authenticated!')
}
})
Appreciate any thoughts on the problem -- thanks!
I've figured out the issue which has nothing to do with Passport or server setup.
In my client-side API call to login I've been using fetch() but I did not have credentials: 'include' option on the initial POST to login. Once I added this option, a cookie was sent back to the client browser and future API calls to isAuthenticated() returned true.
Make sure to have credentials: 'include' on ALL of your API calls, including the initial POST to login.