I am considering using the Passport Library (http://passportjs.org/) for authentication in a Node project.
I am confused by the following passport session functions:
passport.serializeUser(function( user, done ) {
done( null, user.id );
});
passport.deserializeUser(function( id, done ) {
user.get( id, function ( err, user ) {
done( err, user );
});
});
I am wondering:
1) Do these get called for every request that needs to be authenticated? Or are they just called once when the session is first created?
2) How do I access the information that is in "user" from other parts of my script?
3) For requests that need to be authenticated, where do I put any additional logic. eg To check if an allowable user idletime value has not been reached.
Thanks (in advance) for your help
1) serializeUser is called when creating a session for the user (when authentication was successful). This is used to store some sort of identifying information (like a unique user-id) about the user in an Express session.
deserializeUser is called for every request, and takes that piece of identifying information from the session to somehow convert it back to a full user record by means of a database query, perhaps, but that's really up to you: instead of just storing the user id you could also store the entire user record in the session, but it depends on the type of user record and the session storage you're using if that's possible (for example, using express.cookieSession would limit the amount of data you can store in a session).
This is what storing the entire user record could look like:
passport.serializeUser(function(user, done) {
// Here, 'user' is the result of the function called by 'new LocalStrategy()'; when
// you call done() below, that result will be stored in the session.
done(null, user);
});
passport.deserializeUser(function(user, done) {
// Here, 'user' is what's stored in the session by serializeUser()
done(null, user);
});
2) Passport populates req.user which you can use in routes or middleware.
3) You could make a middleware to implement such checks. This might be a good starting point.
Related
I am new to node js and tried passport for authentication, i found it hard to understand the work flow.
so my question is what is actually req.user where it came from ?
is it something constant related to passport and can't be changed to anything else like req.profile ?
Secondly in the following html code
<p>
<strong>id</strong>: <%= user._id %><br>
<strong>username</strong>: <%= user.local.username %><br>
<strong>password</strong>: <%= user.local.password %>
</p>
From where html is populating user object, neither my database schema nor my passport contain the word user
This is my database schema
local:{
username : String,
password : String
}
Thanks
I'll show you where req.user comes from without using passport. First thing you need to understand is a middleware in express is just a function that takes in request, response and a next function.
let's say I have an endpoint:
POST /auth/login that takes a username and password.
This endpoint could return an access token (random string generated by you and stored on the database, if you don't want to store on your database you could look into JWT).
Ok, now that you have that access token after login success.
You can pass it along with other requests to your server.
Let's say another endpoint:
GET /auth/profile which is protected and only can be access with the right access token.
But what is protecting the route? It's a middleware.
Below we define a checkAuth middleware.
function checkAuth(req, res, next) {
// here I can retrieve accessToken from the request header
const accessToken = req.get('accessToken')
// with this accessToken I can query the database and check if this access token is correct or not
findUserWithTheAccessToken(accessToken)
.then(user => {
if (user) {
// Here's the answer to your question where `req.user` comes from.
req.user = user
next() // call next() so it can go to the request handler
} else {
throw new Error('Invalid access token.')
}
})
}
// then your router would look something like the following
router.get('/auth/profile', checkAuth, handler) // implement your handler ☺️
You can always check express website to learn more.
passport=> Passport is authentication middleware for Node.js. and its easy to implement.
login page send username and password to passport.
It will check with your database which is configured by you.
get success then the passport will store user details into req session form that you can retrieve it wherever you want by using your req
As shown in the illustration below, I have a standalone API Project running on a server with a port say 3001, and I have a Web App running on a server with port say 3002.
API on port 3001, has all the API routes required for the Web App (& mobile apps) to fetch and put data, including Authentication API (Using passport-local and passport-jwt). In the API side of the project, I have also handled user role authorization, and every routes has list of roles who can access the APIs.
Example Route
todoRoutes.get('/',
requireAuth,
AuthController.roleAuth(['user','editor','admin']),
TodoController.getTodos);
Role Authorization API Method in port 3001
exports.roleAuth = function(roles){
return function(req, res, next){
var user = req.user;
User.findById(user._id, function(err, foundUser){
if(err){
res.status(422).json({error: 'No user found.'});
return next(err);
}
if(roles.indexOf(foundUser.role) > -1){
return next();
}
res.status(401).json({error: 'You are not authorized to view this content'});
return next('Unauthorized');
});
}
}
Response json after login successfully is like this
{
"token": "JWT eyJhbGci...",
"user": {
"_id": "5986b81d940bab06ddc79b34",
"email": "myemail#gmail.com",
"role": "admin"
}
}
Now in Web App, I want to use same role authorization and authentication (login), but you see, Web App is not connected to database, for me to make queries like check if the user in session is valid and has the role as in the response it got after login successfully.
Summary
Here are bullet points of what I was looking for in this question:
Login on Client-Side Web Application, via Remote API on port 3001 (achieved)
Get User Token and other information (response shown above) (achieved)
Ensure user is authenticated on Client-Side Web App and also remember role of the user loggedin, to use these information for authorization of every routes on Client-side app. In client-side app I have few pages with forms to send data to Server-Side API on port 3002, these pages are used by two different user with roles editor and admin.
TIA
Your authenticating API should return a JWT with guaranteed information (the role) embedded. Further, the token should be made using a secret known to your view APIs.
For example, using npm module jsonwebtoken, sign it like so:
token = jwt.sign( {
exp: Math.floor( Date.now() / 1000 ) + ( 60 * 60 ), // 1 hour
i: user._id,
role: user.role
}, "my-secret" );
Then, on your view API, use passport-jwt, which both verifies the token and provides you a payload that matches the original object you signed. Use the payload as the user object:
passport.use( new JwtStrategy( {
secretOrKey: "my-secret"
}, ( payload, callback ) => callback( null, payload ) ) );
const authenticate = () =>
passport.authenticate( "jwt", { session: false, failWithError: true } )
At this stage, your user is at minimum authenticated. If you want to restrict a view to certain roles, you would add a second middleware:
const assertRole = ( ...roles ) => ( req, res, next ) =>
req.user && roles.includes( req.user.role ) ? next() : res.sendStatus( 403 ) );
todoRoutes.get("/admin/view1", authenticate, assertRole( "user", "editor", "admin" ), TodoController.getTodos );
If your view is going to be need more information about the user, the authentication API will need to provide that, either in the JWT (and thus guaranteed) or outside (not guaranteed, but results in smaller tokens).
exports.roleAuth = function(roles){
return function(req, res, next){
var user = req.user;
User.findById(user._id, function(err, foundUser){
if(err){
res.render('index.html');
}
if(roles.indexOf(foundUser.role) > -1){
res.render('another.html');
}
res.render('another2.html');
});
}
}
There are two ways that I see this can be done, though not perfectly.
One is to use the primary api endpoint of a particular view. Normally each view has a primary endpoint, and if that endpoint returns an Unauthorized / Forbidden status, you shouldn't render the view. But this solution has problems and not always there's a primary endpoint matching a view.
Other option is to namespace the routes with roles, like admin/dashboard and users/dashboard, and the user should have a field describing their role, eg. user.role. And before rendering the views, check for the respective role in url and in user object returned by the API.
The second option is preferred and this is what I generally use.
Hope it helps.
Doesn't the beauty of JWTs solve this issue already, or rather can solve your issue with the correct implementation?
All that is required is for you to
initialise the passport-jwt module in your WebApp in the same way you initialise it in your API, namely the secretOrKey parameter.
inspect the JWT, specifically the role property and allow/reject based on that
I am using the latest version of Express (4.x) and Passport.js (0.13) in my Node.js application. I can get the current user object inside of a route by using req.user and working with the object, which works. However, for situations outside of routing, is there a method I can call or global object I can access which contains the same information?
The reason I want to do this is I have a Socket.io listener which waits for a message string to be sent. It then gets the currently logged in user, gets their ID and uses that for the database association. This takes place outside of a route obviously.
Passport.js uses session to deserialize the user and have it store at express.js req object. So to authenticate the user in Socket.io, you need to find the session using the cookie, lookup the session in the session store and finally get the user from the session.
You can use Socket.io middlewares to achieve this. Here is a pseudo-code to get you started:
var io = require('socket.io')();
io.use( (socket, next) => {
if (socket.request.headers.cookie) {
// find the session id in cookie
const sessionID = socket.request.headers.cookie['connect.sid']
// lookup the sessionID in session store
MongoStore.get(sessionID, (err, session) => {
// get the userID from the session
const userID = session.passport.user;
// Lookup user using the UserID
User.find(userID, (err, user) => {
//save the user in socket
socket.user = user;
next();
})
})
}
next(new Error('Authentication error'));
});
I've seen a few questions regarding this but being new to both Angular and Node I'm struggling to find a proper solution.
I had this code, which worked for the login and I could only access my pages after being authenticated:
router.post('/login',
passport.authenticate('local',
{
successRedirect: '/',
failureRedirect: '/login',
failureFlash: false,
successFlash: false
}
));
router.all('*', ensureAuthenticated);
function ensureAuthenticated(req, res, next) {
console.log("in ensureAuth", req.isAuthenticated());
console.log("session data", req.session);
console.log("user data", req.user);
if (req.isAuthenticated())
{
return next();
}
res.redirect('/login');
}
The local passport authentication I have returns an object after verifying if the user exists and allows login, as such:
return passportDone(null, result.rows[0]);
with result.rows[0] being the user info I want.
What I had in the front end is a simple ng-submit in the form that calls the "login(credentials)" with credentials being set by ng-model in their respective fields (username and password).
My question is how can I return all the info I want, such as user_role, name and stuff, so I can present it in the front-end as {{user.name}} for example?
The info needs to stay after refreshes so $scope isn't an option from what I've read.
What you probably want to do here, is create a custom route for fetching this information. for example: "/me".
In this route, you can serve the information you now probably store in your session variable.
Another solution, depending on how your application works with authentication, is to inject the userinfo (For example if you log in, and get redirected to a new page, you can inject the userinfo into the new page as a js variable) or to return the userinfo in the response if you send a ajax request to login and dont get redirected to a new page by the server.
I hope you are using passport.js local authentication.I would recommend to store user infomation after authentication in cookies as a json webtoken or using some other encryption.And you should expose an api(/api/is-authenticated) which is used to check whether the user is authenticated, by sending the token stored in cookies.when ever you refresh or navigate to other routes make an api(/api/is-authenticated) call to check whether that particular user has already authenticated or not.
Ok, I got it.
I already had the service and all but the problem was with passport's successfulRedirect. It returns the html you want when it succeeds, which is fine. However, since I wanted the user info, it wasn't enough. What I did was create an /account route that returns the req.user info, which I then handle in the front end and form a cookie with it. What basically happens is:
post_login->verify_user->success->get_account->form_cookie->render_html
Whenever a model is created (or deleted/modified) every connected socket is notified through Sails autowatch setting. That's fine to some extent, but I'd like to filter these notifications at some point.
My application has its own "Notifications" which should be sent to their respective receiver. So their anatomy is somewhat like: id, message, receiver, sender.
Authentication is a local passport implementation.
Listening for notification events result in getting notified every time a notification is created.
// client: app.js
io.socket.on('notification', function(evt) { console.log(evt); });
What I try to achieve now is to filter these notifications to match the user id. I've written a policy which gets applied to the /notification events.
// Policy: forUser
module.exports = function(req, res, next) {
// ... whatever ... //
return next();
}
Within the policies
'notification': {
'create': ['passport', 'forUser']
}
My problem now is: how to implement this policy? I thought of just checking for notification.receiver == req.user.id, but how to get the notification model within the policy (if this is the right way at all)?
Thanks.
Edit: Tried implementing the room solution, but I don't get any notifications on the client.
I've altered my subscribe function within my NotificationController:
subscribe: function(req, res) {
sails.log.info('Your user id: ' + req.user.id);
sails.sockets.join(req.socket, 'user_notifications_' + req.user.id);
res.json({
room: 'user_notifications_' + req.user.id
});
},
And added a afterCreate method to my model:
afterCreate: function(model, next) {
sails.sockets.broadcast('user_notifications_' + model.receiver, { test: 'hello' });
next();
}
Code on client is now:
io.socket.get("/notification/subscribe", function(data, jwr) {
io.socket.on(data.room, function(obj) {
console.log(obj);
});
});
The subscription method is called and returns the right room name. But I don't get any messages when calling /notification/create?message=test&receiver=1. The afterCreate method is called, all user ids are right (since there's only one user), but nothing happens.
Edit2:
It seems like joining the rooms fails.
sails.sockets.join(req.socket, 'testroom');
// For testing
sails.log.debug(sails.sockets.socketRooms(req.socket));
The room gets created, but the socket is not subscribed to it.
Edit3:
Found the solution. I'll post the GitHub link as soon as the interface is done.
Are you using sails.sockets.blast() to send your notifications?
To send custom events, you could use sails.sockets.emit()
// Controller action
actionSendingNotification: function(req, res) {
// Retrieve the user socket ID and the data to send
// ...
sails.sockets.emit(userSocketId, 'notification', data);
}
You have to be able to know if the user has an active websocket connection and retrieve his socket ID. He could have several tabs opened in his browser and several websocket connections ...
A probably better solution would be to use sails.sockets.join() and sails.sockets.broadcast(). You would then create the association between the connected user and the observed event within socket.io.
// When the user creates a websocket connection, subscribe him to the model room
// This action MUST be called by a websocket request
// Here I assume your event is related to a model
subscribeToModelRoom: function(req, res) {
var roomName = 'myModelRoom' + req.param('model_id');
sails.sockets.join(req.socket, roomName);
res.json({
message: 'Subscribed to a room called '+roomName+'!'
});
}
Then every time you send a message to the room, the subscribed user will receive it.
// When the model has to send a notification, use broadcast() to send it to the room associated to the model.
sails.sockets.broadcast('myModelRoom' + modelInstance.id, 'notification', data);
Edit
Reading your question again, I will add some explanations.
It seems that you try to send your notification to a user, based on his user.id. You cannot assume that this user will be connected via websocket when you send this notification. You don't send events to a user, but to a opened websocket connection (that may be authenticated).
If the user must not miss the notification, you have to store it in a database. You will then show it to the user when he will be connected.
If you want the user to be informed in real time while he is connected, you could subscribe him to a room "user_notifications_" + user.id when you initialize his authenticated websocket connection. Then emit an event to this room when you create a new notification.
You could add the logic to manage seen / not seen notifications and delete the obsolete records.
This way, you can send information to your users in real time and you will not lose the information if nobody is here to receive it.