I'm new to saml and using Nodejs + Express + passport-saml + okta identity provider. I know this is a duplicate question but somehow I am not able to solve this by looking lot of threads on the internet.
I used yeoman express generator for project. Here are my settings:
Server is behind ngnix using https. So, if I hit https://mywebsite.com, it redirects internally to localhost:3000 on that server.
express.js
var samlUtil = require('./saml-util.js');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
limit: '2mb',
extended: true
}));
app.use(compress());
app.use(cookieParser(config.SERVER_KEYS.SERVER_SECRET));
app.use(express.static(config.root + '/public'));
app.use(methodOverride());
app.use(session({
secret: config.SERVER_KEYS.SERVER_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
expires: false,
secure: true
}
}));
app.use(samlUtil.initialize());
app.use(samlUtil.session());
app.get('/saml/response', samlUtil.protected, function(req, res) {
res.end("Hello " + req.session.passport.user);
});
app.get('/saml/invalid', function(req, res) {
res.end("Authentication failed");
});
app.post('/saml/callback', samlUtil.authenticate('saml', {
failureRedirect: '/saml/response/',
failureFlash: true
}), function(req, res) {
req.session.save(function() {
res.redirect('/saml/response/');
})
});
app.get('/saml/login', samlUtil.authenticate('saml', {
failureRedirect: '/saml/response/',
failureFlash: true
}), function(req, res) {
res.redirect('/saml/response/');
});
saml-util.js
var path = require('path');
var passport = require('passport');
var root = path.normalize(__dirname + '/../..');
var constant = require(root + '/app/util/constants.js');
var config = require(constant.APP_CONFIG_FILE);
var SamlStrategy = require('passport-saml').Strategy;
var users = [];
function findByEmail(email, fn) {
for (var i = 0, len = users.length; i < len; i++) {
var user = users[i];
if (user.email === email) {
return fn(null, user);
}
}
return fn(null, null);
}
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function(user, done) {
console.log('serializing');
done(null, user.email);
});
passport.deserializeUser(function(id, done) {
console.log('de-serializing');
findByEmail(id, function(err, user) {
done(err, user);
});
});
passport.use(new SamlStrategy({
issuer: config.SAML.ISSUER_URL,
path: config.SAML.PATH,
entryPoint: config.SAML.ENTRY_POINT,
cert: config.SAML.CERTIFICATE,
}, function(profile, done) {
console.log('got profile');
console.log(profile);
if (!profile.email) {
return done(new Error("No email found"), null);
}
process.nextTick(function() {
console.log('Finding by email');
findByEmail(profile.email, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
console.log('new user');
users.push(profile);
return done(null, profile);
}
console.log('existing user');
return done(null, user);
})
});
}));
passport.protected = function protected(req, res, next) {
console.log('is isAuthenticated =' + req.isAuthenticated());
if (req.isAuthenticated()) {
return next();
}
res.redirect('/saml/invalid');
};
exports = module.exports = passport;
What is happening:
I can hit the URL: /saml/login
Gets redirected to the okta login page (where I have identity settings)
I login successfully
I'm redirected to the URL: /saml/callback with response:
{issuer:
{ _: 'http://www.okta.com/exkctyzcknbMikNjl0h7',
'$':
{ Format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity',
'xmlns:saml2': 'urn:oasis:names:tc:SAML:2.0:assertion' } },
sessionIndex: '_3acb290873febaf825cd',
nameID: 'ashutosh#myemail.com',
nameIDFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
nameQualifier: undefined,
spNameQualifier: undefined,
firstName: 'Ashutosh',
lastName: 'Pandey',
email: 'ashutosh#myemail.com',
getAssertionXml: [Function] }
In the /saml/callback URL, I can see value returned in req.user but
req.isAuthenticated() in saml-util is always returning false.
Related
so i have this problem with passportJS local strategy
it can't persist logins when i wrap passport.athenticate inside a function
when i try to login on the route /loginT then go to /whoami it works just fine
but when i use /login it responds with user object , but when i hit /whoami it log undefined
i think i made a mistake somewhere but i can't find what's wrong with it
thank you
here's passport config
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy
const UserDB = require('../models/users')
const { UserStatus } = require('../middleWares')
const signIn = new LocalStrategy (
function( username , password , callback) {
UserDB.findOne(
{$or : [{username:username},{email:username}]},
async function(err,user){
if (err) callback({
success:false,
code:403,
message:err,
}, false) ;
if (!user)
callback({
success:false,
code:403,
message:'Username not found',
}, false)
if (!(await user.verifyPassword(password)))
callback ({
success:false,
code:403,
message:'Username or Password is incorrect',
},false)
if(user.status === UserStatus.PENDING_EMAIL_ACTIVATION) {
callback({
success:false,
code:93,
message:'Please Activate your account first',
},false,)
}
callback(null,user)
}
)
}
)
passport.use('local-signin',signIn)
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
UserDB.findById(id, function (err, user) {
done(err, user);
});
});
module.exports = passport;
userRoute file
router.post('/loginT',passport.authenticate('local-signin',{
failureRedirect:'/Login',
successRedirect:'/'
}))
router.get('/whoami',async (r,res) => {
console.log(r.user)
return res.send({user:r.user})
})
router.post('/login',async (r,s)=>{
passport.authenticate('local-signin',{},function(err,_user,info){
if(err) s.send(err)
if(_user) s.send({
success:true,
code:200,
data:{..._user._doc,
password:null,
_id:null
}})
})(r,s)
})
server.js
const passport = require('./passport');
app.use(require('express').json())
app.use(cookieParser());
app.use(require('express-session')({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
*** SOLVED ***
i forgot to use request.logIn(user, function)
so i had to make the login route as next
router.post('/login',async function (r,s){
passport.authenticate('local-signin',{},function(err,_user,info){
if(err) s.send(err)
r.logIn(_user,function(error) {
if(error) return s.send({
success:false,
code:403,
message:error,
})
return s.send({
success:true,
code:200,
data:{..._user._doc,
password:null,
_id:null
}})
})
})(r,s)
})
Alright, I've been racking my brain over this for hours now. When I call my 'sign-in' route the passport middleware works fine and returns with a req.user obj, but when I call another route after that, req.user for that other route is undefined. Where exactly have I messed up here? I'm not sure it matters, but I am calling my API routes from a react client.
auth
router.post(
"/sign-in",
passport.authenticate("local"),
async (req, res, next) => {
if (!req.user) console.log("NO USER!*******************");
try {
const user = _.get(req, "user", "");
res.status(200).json(user);
} catch (e) {
console.log({ e });
return res.status(400).json(false);
}
}
);
My Server
app.use(cors());
app.use(cookieparser());
app.use(logger("dev"));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(
session({
secret: "super",
resave: false,
saveUninitialized: true,
cookie: { secure: false, maxAge: 4 * 60 * 60 * 1000 }
})
);
require("./utils/passport");
app.use(passport.initialize());
app.use(passport.session());
./utils/passport
const _ = require("lodash");
const LocalStrategy = require("passport-local").Strategy;
const { PrismaClient } = require("#prisma/client");
const prisma = new PrismaClient();
const bcrypt = require("bcrypt");
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(async function(id, done) {
console.log("");
console.log("deserializeUser*************************");
console.log("");
try {
const user = await prisma.user.findOne({ where: { id } });
if (!user) {
return done(null, false);
}
return done(null, user);
} catch (e) {
done(e);
}
});
passport.use(
new LocalStrategy(
{
passReqToCallback: true,
usernameField: "email"
},
async (req, email, password, done) => {
try {
const user = await prisma.user.findOne({ where: { email } });
if (!user) {
return done(null, false);
}
await bcrypt.compare(
password,
_.get(user, "password", ""),
(err, result) => {
if (!result) {
done(null, false);
}
done(null, user);
}
);
} catch (e) {
done(e);
}
}
)
);
I suspect something is wrong with the portion where you have your post method with passport authenticate.
This portion may be deleted:
if (!req.user) console.log("NO USER!*******************");
I think the asynchronous may produce here odd results. Then for the sake of troubleshooting, include in your
const user = _.get(req, "user", "");
real data, such as real user data. Thus you will be able to verify that the data is passing through.
How use nuxt auth Module (front-end) with passport-local using JWT (back-end express) ?
defining jwt strategy for verify jwt token (express)
var JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'secret';
opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
});
}));
defining local strategy for verify username nad password (express)
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
if (!user.verifyPassword(password)) { return done(null, false); }
return done(null, user);
});
}
));
code for issuing token after verifying username and password (expresss)
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login' }), //need to update from nuxt auth.
function(req, res) {
res.redirect('/');
});
nuxt auth local strategy consume username and passsword returns a JWT token (nuxt)
this.$auth.loginWith('local', {
data: {
username: 'your_username',
password: 'your_password'
}
})
It can work independently how do i combine these ?
code for express
Create passport strategies
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JwtStrategy = require('passport-jwt').Strategy;
passport.use(
new LocalStrategy(
{
usernameField: 'username',
passwordField: 'password'
},
function(username, password, done) {
users.findOne({ email: username }, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { error: 'Invalid username' });
}
if (!user.checkPassword(password)) {
return done(null, false, { error: 'invalid password' });
}
const info = { scope: '*' };
done(null, user, info);
});
}
)
);
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'JWT_SECRET_OR_KEY';
passport.use(
new JwtStrategy(opts, function(payload, done) {
users.findById(payload, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
}
return done(null, false);
});
})
);
use passport strategies
const express = require('express');
const passport = require('passport');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(passport.initialize()); // Used to initialize passport
// Routes
app.post(
'/login',
passport.authenticate('local', { session: false }),
function(req, res) {
const token = jwt.sign(req.user.userId, 'JWT_SECRET_OR_KEY');
return res.json({ token });
}
);
app.get(
'/me',
passport.authenticate(['jwt', 'bearer'], { session: false }),
function(req, res, next) {
const { userId } = req.user;
users.findOne({ _id: userId }, (err, data) => {
if (err) {
res.status(500).send(err);
} else if (data) {
const userData = data;
res.status(200).send(userData);
} else {
res.status(500).send('invalid token');
}
});
}
);
configuration for nuxt
inside nuxt.config.js
auth: {
resetOnError: true,
redirect: {
login: '/login', // User will be redirected to this path if login is required.
home: '/app/dashboard', // User will be redirect to this path after login. (rewriteRedirects will rewrite this path)
logout: '/login', // User will be redirected to this path if after logout, current route is protected.
user: '/user/profile',
callback: '/callback // User will be redirect to this path by the identity provider after login. (Should match configured Allowed Callback URLs (or similar setting) in your app/client with the identity provider)
},
strategies: {
local: {
endpoints: {
login: {
url: '/login',
method: 'post',
propertyName: 'token'
},
logout: false,
user: {
url: '/me',
method: 'GET',
propertyName: false
}
},
tokenRequired: true,
tokenType: 'Bearer'
}
}
inside Login .vue
this.$auth
.loginWith('local', {
data: {
username: this.user.email,
password: this.user.password
}
})
.catch(err => {
console.error(err );
});
I have a simple Node+Express application with Passport Local authentication, it's working nearly perfectly (the right pages are served up, login/logout works, etc).
One issue though is that if a second user logs in, that session "takes over" the first user's session and suddenly the first user is now logged in as the second user!
This is almost certainly an issue with the order of the middleware, but I haven't been able to find it - I'm fairly new to Passport and Express. The code is taken liberally from docs and examples, but I might have messed the order up.
I've followed the order in this answer but it's still not working. Can anyone suggest where I might be mixed up?
The main server:
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
var url = require('url');
var db = require('./db');
// Configure the local strategy for use by Passport.
passport.use(new Strategy(
function(username, password, cb) {
db.users.findByUsername(username, function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
if (user.password != password) { return cb(null, false); }
return cb(null, user);
});
}));
// Configure Passport authenticated session persistence.
passport.serializeUser(function(user, cb) {
console.log('DEBUG: serializeUser called ' + user.id);
cb(null, user.id);
});
passport.deserializeUser(function(id, cb) {
console.log('DEBUG: deserializeUser called ' + id);
db.users.findById(id, function (err, user) {
if (err) { return cb(err); }
cb(null, user);
});
});
// Create a new Express application.
// Use application-level middleware for common functionality (Order is important!)
// Initialize Passport and restore authentication state, if any, from the session.
var app = express();
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'qwerty', resave: false, saveUninitialized: false })); // Step 2.
app.use(passport.initialize());
app.use(passport.session());
app.post('/login',
passport.authenticate('local', { failureRedirect: '/login.html' }),
function(req, res) {
console.log('Logged In: ' + req.user.username);
res.redirect('/');
});
app.get('/logout', function(req, res){
if (req.user != null) {
console.log('Logged Out: ' + req.user.username);
req.logout();
} else {
console.log('Warning: null user logging out');
}
res.redirect('/login.html');
});
app.get('/user', function(req, res){
if (req.user != null) {
res.send(req.user.displayName);
} else {
res.send('ERROR: No current user?');
}
});
function isAuthenticated(req, res, next) {
if (req.user) { // User is logged in
return next();
}
if (req.url.startsWith('/login')) { // Allow login through to avoid infinite loop
return next();
}
res.redirect('/login.html');
}
app.use('/', isAuthenticated, express.static('/public/'));
The db/users.js file:
var records = [
{ id: 1, username: 'jill', password: 'birthday', displayName: 'Jill'}
{ id: 2, username: 'jack', password: 'hello', displayName: 'Jack'}
];
exports.findById = function(id, cb) {
process.nextTick(function() {
var idx = id - 1;
if (records[idx]) {
cb(null, records[idx]);
} else {
cb(new Error('User ' + id + ' does not exist'));
}
});
}
exports.findByUsername = function(username, cb) {
process.nextTick(function() {
for (var i = 0, len = records.length; i < len; i++) {
var record = records[i];
if (record.username === username) {
return cb(null, record);
}
}
return cb(null, null);
});
}
Tumbleweeds!
So it turns out that it was working fine - the problem was just that Chrome reuses its session across tabs. I found a helpful comment buried in this question's answer. One login from Chrome and another from IE works fine, no conflict.
Node + Passport - multiple users
I am using Sails version 0.11 and trying to configure app with jwt authentication.
config/passport.js
var passport = require('passport');
var jwt = require('express-jwt');
module.exports = {
http: {
customMiddleware: function(app) {
console.log('express midleware for passport');
app.use(jwt({ secret: sails.config.session.secret, credentialsRequired: false}).unless({path: ['/login']}));
app.use(passport.initialize());
app.use(passport.session());
}
}
};
services/passport.js
/* other code */
passport.serializeUser(function(user, done) {
console.log(user);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log(id);
findById(id, function(err, user) {
if (err)
done(err);
else if (!user) {
done(null, false);
}
else
done(null, user);
});
});
AuthController.js
module.exports = {
login: function(req, res) {
passport.authenticate('local', function(err, info, user) {
if (err) {
return res.send(err);
}
else if (!user) {
return res.send(info);
}
else {
var token = jwt.sign(user, sails.config.session.secret, {
expiresInMinutes: 5 // expires in 5 minutes
});
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
})(req, res);
},
me: function(req, res) {
res.send(req.user);
}
}
Why is my desesializedUser function never called? What is wrong in my code?
Do you configure the strategy for passport?
In the passport doc, the below is mentioned.
Before authenticating requests, the strategy (or strategies) used by an application must be configured.