I'm protecting an API in my web app using passport. Before hitting the resource, I want the user(from oauth client) to login/authorize first.
Have tried both LocalStrategy and BasicStrategy - their implementation are almost 100% the same, just look for a user by email and verify password. If I use BasicStrategy, the browser will bring up a dailog saying 'Authentication Required' and ask for username & password. However if I use LocalStrategy, it just says 'Unauthorized', and no chance to login.
So my questions are:
How does browser decide to bring up the login dialog, or is it done by BasicStrategy?
Is it possible to show a login page with some UI, instead of the simple dialog?
Note that it's part of OAuth process so I don't really want to redirect to the login page.
Passport is used only for authentication. Later after one needs to maintain session , to check if user is logged in or not.
So you can make a middleware call before every route.
Middlleware checking if user is logged in or not .
'use strict';
var express = require('express');
module.exports = {
isLoginCheck : function (request, response, next) {
if(!request.session.user && request.path != '/login'){
response.redirect('/login');
}else{
next();
}
},
};
In routes file import the middleware,
var express = require('express'),
indexController = require('./../controller/index'),
middleware = require('./../middleware/index'),
passport = require('passport'),
router = express.Router();
router.get('/addUser', indexController.addUser);
router.post('/saveUser', indexController.saveUser);
router.use(middleware.isLoginCheck);
router.get('/', indexController.index);
router.get('/login', indexController.login);
router.post('/login', function(request, response, next){
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
var message = "Invalid credentials";
return response.render('login',{message: info.message, userLoggedIn : null});
}
request.logIn(user, function(err) {
if (err) { return next(err); }
request.session.user = request.user;
response.redirect('/userList');
});
})(request, response, next);
});
Note : in the success we are saving the verified user in session , which will persist till the server is running. Middleware above we are checking for the session object.
Turned out the Authentication dialog is the sequela of response header:
The browser detects that the response to my call on the XMLHttpRequest object is a 401 and the response includes WWW-Authenticate headers. It then pops up an authentication dialog asking, again, for the username and password.
Here's a very helpful post.
As for the standalone signin page, because it's part of OAuth process, it's better to use express's redirect.
Related
I'm a little new to this. I have REST API made with Node.js and Express.js. Some routes have authentication middleware. To use those routes, a header has to be set with the user's auth token which gets verified. I have been doing this with no problem with static sites using local storage. I'm making my first dynamic site now (using Express) and for certain routes I have middleware that loads all the data I need to display the page. How do I access and use auth tokens now that I don't have local storage's help?
EDIT(for clarification):
So here is one of my api routes that fetches all transactions from a database(mongoDB).
app.get('/transactions', authenticate, (req, res) => {
Transaction.find().then((transaction) => {
res.send({transaction});
}, (e) => {
res.status(400).send();
});
});
This is the authentication middleware that gets run.
var authenticate = (req, res, next) => {
var token = req.header('x-auth');
User.findByToken(token).then((user) => {
if (!user) {
return Promise.reject();
}
req.user = user;
req.token = token;
next();
}).catch((e) => {
res.status(401).send();
});
};
Now on my express webserver, I have a following route, where I use getTransactions to fetch all my data. (which I display with handlebars)
router.get('/orders', getTransactions, (req, res) => {
res.render('orders.hbs', {
transaction: req.transactions.data.transaction
});
});
and this is the middleware
var getTransactions = (req, res, next) => {
axios.get('https://serene-wave-28270.herokuapp.com/transactions')
.then((response) => {
req.transactions = response;
console.log(req.transactions.data.transaction);
next();
}).catch((e) => {
console.log(e);
})
}
So when I was just making a static site without using express as a webserver, I would just have the user sign in and save the auth token in local storage. Also, I should note that the first two blocks are from my api, and the bottom two from webserver, both hosted separately on Heroku. I'm not sure if that's standard design so I thought I should mention it.
There's not a whole lot of detail in your question for exactly what you're trying to do, but I can explain the general concepts available to you in Express:
The usual scheme for Express is to authenticate the user initially and then set a session cookie that indicates that user has been authenticated. Since the cookie is automatically stored by the browser and then automatically sent from the browser to the server with every request, you will have that cookie which the server can then use to identify a server-side session and then you can use any info you want from the session (user identify or other state you store in the session object) when creating pages or responding to API requests for that user.
The NPM module express-session handles much of this work for you as it will automatically create a session object, a session cookie and hook the two together on every request.
If, on the other hand, you already have an auth token in the client and you just want that to be automatically communicated to the server with every request, then you can just put that auth token into a cookie and have the server look for it in the cookie on each request. You can even make it a bit more secure by setting the cookie to HttpOnly so that the auth token cannot be accessed from client-side Javascript (this will not affect the server's ability to access it).
There is not much detail in your question but here are a few thoughts.
You can either use cookies (as detailed by #jfriend00 below) or use the requests' headers to check for a valid authorization token (which I describe below)
In Express you can access the headers through req.headers so you can just write a middleware that you will call before your current middleware loading all the data to ensure that the user is authorized to continue (calling next() to call the next middleware) or using a custom Error type to flag the authentication error if he is not (calling next(err) to skip all the other middleware and jump to your error middleware)
For example (assuming you have a subclass of Error named AuthorizationError defined somewhere):
const express = require('express');
const AuthorizaztionError = require('<some path>');
const app = express();
function checkAuthTokenMiddleware(req, res, next) {
if (req.headers && req.headers.authorization) {
let token;
const parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
const [scheme, credentials] = parts;
if (/^Bearer$/i.test(scheme)) { // or any other scheme you are using
token = credentials;
}
if (token === undefined) {
// access token - missing
return next(new AuthorizationError(
"Invalid access token.", // error_description
"invalid_token" // error
));
}
// add something here to ensure the token is valid
return next();
}
} else {
// No authorization header => invalid credentials
return next(new AuthorizationError(
"Authorization header required.", // error_description
"invalid_request" // error
));
}
}
// Add this in your route declaration
app.use(
"/auth/test",
checkAuthTokenMiddleware,
function(req, res, next) {
// do something
}
);
// this must come last
app.use(function errorMiddleware(err, req, res, next) {
// return something
if (err instanceof AuthenticationError) {
// do something for example
res.status(401).send(err.error_description);
} else {
// generic error handling, for example
res.status(500).send("Error "+err);
}
})
// ...
Background
I have a MEAN application with CRUD capabilities fully tested with postman. I have been trying to persist login for quite some time now with no luck. I have read and tried the following
passport docs
toon io's blog about login
Express session
Scotch io, node auth made easy
Plus a buch of other light reading materials (Lots of SO questions)
But I have only been able to register and log a user in, not persist login with a session.
My App
Here is a link to the full github repo (if you are looking for the latest changes check develop branch)
My Understanding of Auth/Login
Here is my understanding of user login with code examples from my project and screenshot of postman results as well as console logs.
Passport setup
I have the following auth.js file, it configs passport
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
This gets called in the server file like
//code before
var user = require('./models/user.js');
var auth = require('./modules/auth.js')(app, user);
// code after
Routing for login
In my routes I have the login route as follows
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({
err: info
});
}
req.logIn(user, function(err) {
if (err) {
return res.status(500).json({
err: 'Could not log in user'
});
}
res.status(200).json({
status: 'Login successful!'
});
});
})(req, res, next);
});
This route works as tested with postman. I enter the details 'joe' and 'pass' and get the following response.
When this route is hit we can also see in the console that the user is serialized.
So what next?
This is where I get lost. I have a few questions.
Is the user now in a session on my server?
Should I send the req.session.passport.user back to the client?
Do I need the session ID on all future requests?
Testing the Session
I have a second route setup for testing the session it is as follows
router.get('/checkauth', passport.authenticate('local'), function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
The part passport.authenticate('local') (I thought) is there to test if the user session exists before giving access to the route but I never get a 200 response when I run this, even after a login.
Does this route expect a req.session.passport.user passed in the head or as a data argument on a http request that requires auth?
If I missed anything or am understanding something wrong please tell me, any input is appreciated. Thanks all.
Is the user now in a session on my server?
No, You need to use the express-session middleware before app.use(passport.session()); to actually store the session in memory/database. This middleware is responsible for setting cookies to browsers and converts the cookies sent by browsers into req.session object. PassportJS only uses that object to further deserialize the user.
Should I send the req.session.passport.user back to the client?
If your client expects a user resource upon login, then you should. Otherwise, I don't see any reason to send the user object to the client.
Do I need the session ID on all future requests?
Yes, for all future requests, the session id is required. But if your client is a browser, you don't need to send anything. Browser will store the session id as cookie and will send it for all subsequent requests until the cookie expires. express-session will read that cookie and attach the corresponding session object as req.session.
Testing the Session
passport.authenticate('local') is for authenticating user credentials from POST body. You should use this only for login route.
But to check if the user is authenticated in all other routes, you can check if req.user is defined.
function isAuthenticated = function(req,res,next){
if(req.user)
return next();
else
return res.status(401).json({
error: 'User not authenticated'
})
}
router.get('/checkauth', isAuthenticated, function(req, res){
res.status(200).json({
status: 'Login successful!'
});
});
As #hassansin says you need to use a middleware that implement session management. The passport.session() middleware is to connect the passport framework to the session management and do not implement session by itself. You can use the express-session middleware to implement session management. You need to modify your auth.js in the following way
var passport = require('passport');
var session = require('express-session');
var LocalStrategy = require('passport-local').Strategy;
module.exports = function(app, user){
app.use(session({secret: 'some secret value, changeme'}));
app.use(passport.initialize());
app.use(passport.session());
// passport config
passport.use(new LocalStrategy(user.authenticate()));
passport.serializeUser(function(user, done) {
console.log('serializing user: ');
console.log(user);
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
user.findById(id, function(err, user) {
console.log('no im not serial');
done(err, user);
});
});
};
Notice that in this case the session engine is using the in memory store and it didn't work if you scale your application and apply load balancing. When you reach this development state something like the connect-redis session store will be needed.
Also notice that you need to change the secret value used on the session midleware call and use the same value on all application instances.
As per the passport documentation, req.user will be set to the authenticated user. In order for this to work though, you will need the express-session module. You shouldn't need anything else beyond what you already have for passport to work.
As far as testing the session, you can have a middleware function that checks if req.user is set, if it is, we know the user is authenticated, and if it isn't, you can redirect the user.
You could for example have a middleware function that you can use on any routes you want authenticated.
authenticated.js
module.exports = function (req, res, next) {
// if user is authenticated in the session, carry on
if (req.user) {
next();
}
// if they aren't redirect them to the login page
else {
res.redirect('/login');
}
};
controller
var authenticated = require('./authenticated');
router.get('/protectedpage', authenticated, function(req, res, next) {
//Do something here
});
I don't know of a way to check all existing sessions, but Passport is handling the issuing of session ids. Try checking that you have req.user on your test endpoint after logging in
Auth0 documentation describes how to set up express-jwt middleware to protect endpoints. The trouble is that the documentation doesn't seem to cover how you get a valid JWT in the first place.
On the angular side, there's documentation on using angular plugins to implement a login page, that's fine. How would one implement a route using express that would take a username/password and return to the client the appropriate JWT such that subsequent requests would be authorized?
I think I may be missing a basic concept about JWT here; via Auth0, when using Username-Password-Authentication, my guess is that Auth0 acts as the repo for those credentials. There's documentation out there about wiring passport to auth0 and JWT, the problem with those is that this documentation assumes that the username/password database is some MongoDB instance locally...I want to avoid that type of setup which was an initial attraction with auth0.
Are there sample projects that cover this, showing how to get a valid JWT on a back-end, without some separate front-end angular app requesting it first?
I use passport.js built in local strategy for authentication and store user information in a JWT that I read on routes that require authorization.
User id's can be serialized/deserialized into and out of the express sessionto obtain the user identifier using the auth token (JWT) in the request. This is in my opinion the best approach since it limits the amount of data stored on the client and provides better security than storing any user information. Here's an example of this in express:
//Set a session secret
var secrets = { sessionSecret: process.env.secret || 'my secret string'};
//Require express-jwt and set a secret for the cookie
var expressJwt = require('express-jwt');
var validateJwt = expressJwt({ secret: secrets.sessionSecret });
//Returns a jwt token signed by the app secret
var signToken = function(id) {
return jwt.sign({
id: id
}, secrets.sessionSecret, {
expiresInMinutes: 60 * 24 // 24 hours
});
};
//Set token cookie directly
var setTokenCookie = function(req, res) {
if (!req.user) {
return res.status(404).json({
message: 'Error during user validation'
});
}
var token = signToken(req.user.id, req.user.role);
res.cookie('token', JSON.stringify(token));
};
//Check to see if user is authenticated (call this when a route is requested)
var isAuthenticated = function(req, res, next) {
// allow access_token to be passed through query parameter as well
if (req.body && req.body.hasOwnProperty('access_token')) {
req.headers.authorization = 'Bearer ' + req.body.access_token;
}
// Validate jwt token
return validateJwt(req, res, next);
};
You can use these methods as middleware in express. Say the above code was token.js, you can force it to execute on each request to a route like this:
app.get('/employee', token.isAuthenticated, employeeController.getEmployees);
I haven't worked with angular but it works great on the backbone projects i've worked on and this process should work with any browser based client that can supply a X-auth cookie on each request. You can do this by using the ajax setup:
$(document).ajaxSend(function(event, request) {
var token = readCookie('token');
if (token) {
request.setRequestHeader('authorization', 'Bearer ' + token);
}
});
Here is an example of middleware that validates a users login and returns a token to the client that can be used on subsequent requests:
var validateLogin = function (req, res, next) {
var username = req.params.username;
// Authenticate using local strategy
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.status(404).json({
info: [{
msg: info.message
}]
});
}
// Send user and authentication token
var token = token.signToken(user.id, user.role);
res.cookie('token', token);
res.render('index', {token: token, user: user});
})(req, res, next);
};
#FrobberOfBits
This is to answer the follow-up Q posted by FrobberOfBits on Feb 6, 2016 at 3:04
I use auth0 for local + social media authentication.
The way auth0 works is, you hand over the approach to authenticate to auth0 ...either it be local with db or social media.
It is a bundled approach where local db and social media authentication is all bundled and provided as a service to you by auth0.
Hope this helps.
I am using nodejs (express) with mongodb and I am trying to figure out how cookies work. I am currently able to let a user login and authenticate it. How do I bring cookies into play and how do I use cookies to query mongodb for the user's info to pull it onto the next page and pages after that, once they login.
Currently I have a route file that posts the login request and then redirects based on success to a userProfile page, I want to include user specific details on that page and then be able to show user other pages and have him return to his unique pages again while querying.
UPDATED CODE: (Can cookie be called the way it is called in the updated code?)
login post route file
exports.loginPost = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err) }
if (!user) { return res.redirect('loginError'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
res.cookie('name', req.params.email, { expires: new Date(Date.now() + 900000), httpOnly: true });
return res.redirect('userProfile');
});
})(req, res, next);
};
read this first What does middleware and app.use actually mean in Expressjs?
then use cookieparser and cookiesession
app.use(express.cookieParser('yoursecretkeyhere'));
app.use(express.cookieSession();
and split your above function into an authentication middleware method and an authenticate POST handler.
Your authentication middleware just needs to check whether the session has an Authenticated flag and if not redirect to your login page. (if this is part of a single page app, just return a 401 and ask for credentials in your browser)
Your authenticate POST handler then checks the posted username and password credentials (or other credentials against a 3rd party api) and sets the session.Authenticated flag followed by a redirect.
I am building a crappy login system as a newbie. I have done this so far:
app.post("/verifyLogin",function(request,response){
var usr=request.body.username;
var pass=request.body.password;
userModel.find({$and:[{username:usr},{password:pass}]},function(err,user){
if(user.length==0)
{
response.redirect("/?msg=failed");
}
else
{
request.session.user=user;
response.redirect("/dashboard");
}
});
});
This works fine but after successful login i want to get the user details in the dashboard. I am clueless. Please shed some light.
EDIT
I have the following setup for dashboard in routes:
app.get("/dashboard",function(request,response){
response.sendfile('/lms/site/dashboard.html');
});
If you mean you want to pass the users' details to a template:
app.get('/dashboard', function(req, res) {
res.render('dashboard', {
user : req.session.user
});
});
This assumes a few things though:
you have a working templating setup;
you have a template called dashboard (with an extension matching your templating setup);
you're going to provide some sort of setup to make sure a user is logged in before they can open /dashboard.
EDIT: since you don't want to use templating, you could use AJAX to get the user details from the server into the client:
// server side
app.get('/userdata', function(req, res) {
// check if a user is logged in
...
// return the user details as JSON
res.send(req.session.user);
});
// client side (in 'dashboard.html', this assumes is will load jQuery)
$.getJSON('/userdata', function(user) {
// process user data, insert it into the DOM somewhere...
});
EDIT 2: to check if a user is logged in, you could create a middleware which would check for the existence of req.session.user and redirect to the login page if it's undefined:
var isLoggedIn = function(req, res, next) {
if (req.session && req.session.user)
next(); // user logged in, so pass
else
res.redirect('/'); // not logged in, redirect to login page
};
You would use the isLoggedIn middleware for all routes that require a user to be logged in:
app.get('/userdata', isLoggedIn, function(req, res) {
res.send(req.session.user);
});