Make route endpoint respond with different content based on user authentication - node.js

Say that I am building a simple web app with a REST back-end where users have their own page with user information.
What I would like to achieve is, if an unauthenticated user makes a request to
www.mywebapp.com/api/user/john
they would be provided with limited information (only age and email for example). But if the users logs in, and makes the same request, the server will also respond with more information (like personal preferences and such).
I am thinking that maybe the middleware that validates the users token, passes on permission on the request (req.role = guest or req.role = user). Then in the user/:name endpoint it would check the role and respond with different content.
The other option would be to make a new route endpoint for authenticated users, and then check which one to call on the client side.
What is best practice here?

This is how I ended up doing:
let router = express.Router();
router.get('/profile', authenticate, hasRole("Admin", true), controller.showProfileAdmin);
router.get('/profile', hasRole("User", true), controller.showProfileUser);
// will not call next('route') if Guest requirements is not met
router.get('/profile', hasRole("Guest"), controller.showProfile);
// hasRole() returns a middleware function that checks if user meets role requirement.
// If nextRoute is true, the function calls next('route'). If nextRoute is false
// or undefined, the function responds with a 403
function hasRole(roleRequired, nextRoute) {
return (req, res, next) => {
// Just checking so that the user has authority for this role.
if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) {
return next();
//else client is not authorized
} else {
// If nextRoute is true, continue to the next route.
if(nextRoute){
return next('route');
//Else respond with a forbidden status.
} else {
res.sendStatus(403);
}
}
}
}

Related

How can I transfer data from one route to another

I implement user registration. I have two routes.
import express from 'express';
import User from '../models/user';
const router = express.Router();
router.post('/signup', async (req, res) => {
try {
...
const user = new User(
red.body.name,
red.body.username,
red.body.user_email,
red.body.user_password,
red.body.user_phone,
);
...
} catch (err) {
return res.status(500).json({
err: err
});
}
});
router.post('/verify', async (req, res) => {
try {
...
console.log(user);
...
} catch (err) {
return res.status(500).json({
err: err
});
}
});
I need to send data from the route, user registration, to the route, user confirmation.
How to send user data to the second route for confirmation?
There are 2 strategies to implement this feature (maybe more):
Strategy 1: Store user in server.
You can store user object and assign it a unique key (e.g. uuid) in server. The user data can be stored as global variable (in memory of Node.js process), or it can be stored in memory database (e.g. Redis) if you are using multiple Node.js process.
In POST /signup route handler, the user data can be written to server, and this newly created user id would be returned to browser. Then, in POST /verify route handler, server would retrieve corresponding user data via its id.
Strategy 2: Store user in browser.
Another strategy is returning the whole user data to browser in POST /signup route handler, and let browser send it back in the following POST /verify request. There are 2 ways to implement this design (maybe more):
Return user data to browser via Set-Cookie. Browser would send user data as cookie automatically.
Return user data to browser as plain response body. Browser take it and save it in localStorage or sessionStorage. Then, when sending POST /verify request, browser would read that data and put it as plain HTTP request body.
The best way to approach this would be to store the user data as a browser cookie. Storing user data in local storage is not recommended as it can be accessed by JavaScript and hence poses a security threat.

How to add current logged in status to Users Schema in MongoDB using Passport and node.js

I’m quite new to backend development…
With using my API I would like to be able to display a list of users and also indicate if they are currently logged in. I got the basic authentification working using passport and json web token
I’m not looking to get the current logged in user.
I want to be able to retrieve a list of users and see if they are logged in or not.
Like this:
var users = Users.find({});
// console.log(users) output:
{
name: 'foo'
password: ...
isLoggedIn: false
},
{
name: 'bar'
password: ...
isLoggedIn: true
},
{
name: 'baz'
password: ...
isLoggedIn: false
}
isLoggedIn would be set to true if the user is currently logged in and to falseif not.
How can I do that? Thank you!
It sounds like what you would like to do is update your MongoDB database based on login/logout events. To do this you could use something like mongoose to work with your Node backend to easily access your database in MongoDB.
You can include mongoose after installing with npm install mongoose like so:
var mongoose = require('mongoose');
var User = mongoose.model('User');
Note that User corresponds to whatever schema you create for storing user information.
Assuming you have some sort of router object for handling requests, you could construct route handlers for /logout and /login and use your imported mongoose User model to retrieve and then modify a specific User object as such:
// whenever user goes to '/login' (you can have, say, your 'login' button make a request to this URL
router.get('/login', function(req,res) {
// your authentication here; passport stores the currently authenticated user in req.user
var username = req.user.name; // here we assume the username is stored as 'name' as you have in your code but change this based on your schema
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = true;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to some page here maybe
}
});
});
});
// whenever user goes to '/logout' (you can have a logout button make a request to this URL
router.get('/logout', function(req,res) {
// currently authenticated user is still in req.user
var username = req.user.name;
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = false;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to login/register page maybe
}
});
});
});
So to summarize what this code would do:
based on the url a user would go to, our route handler would fetch one correct, unique User object from our database based on the name (username)
it would do so by accessing the username property of req.user which corresponds to the currently authenticated user with Passport, which, again will be different for all users
update the field that we use to keep track of login status (isLoggedIn)
and then save the changes, after which we are done updating the state to reflect whether the user is logged in or not, so we can now redirect to some other page or display other content
Finally then, you could retrieve a list of all users similarly to your code like so:
User.find({}, function(err, users, data) {
// all users from your database are in `users`
console.log(users);
});
Edit for expired sessions:
So, to track expired sessions, since you're using Passport, would in theory require functionality to signal with some sort of event / callback / message, etc. the moment the session is deemed invalid. Now that is tough to monitor and from my experience with Passport, stuff like that isn't implemented in all authentication strategies and might vary based on the strategy to be used by developers (think for instance if a browser window is closed, based on Passports authentication strategy, or just browser, it might destroy the cookie for the session right away and our server has no way of knowing about it). I do recommend checking out all the authentication strategies Passport offers in case there are some better ones here.
Now, if you would like to add functionality to track the users passive login/logout status with sessions yourself, you could use something related to cookies. Again, not necessarily one to use, but here's a couple handy Express modules: cookie-parser and cookie-session.
Then, you could set and read cookies like this, using cookie-parser:
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
You would put this code somewhere right after the user is authenticated:
// cookies are stored here
console.log(req.cookies);
// configure your cookie
var options = {
expires: 1000 * 60 * 60, // expires after one hour
httpOnly: true
}
// Set cookie
res.cookie('session', ('user-' + req.user.name), options);
And then, on the client side check if that cookie is valid continuously on some time interval, and if it expired Date.now() > cookie.expires then make a GET request to /logout, and there log out the user (currently still authenticated) by updating MongoDB and all.
However, since this would require making a mechanism to basically simulate an expired session, I would recommend using something analogous to a timeout, which would be much easier to implement. Just a note, this is sort of analogous to mechanisms on some pages you might have encountered where you get a pop-up saying 'You will be logged out due to inactivity'. In your main.js or whatever client-side script define a function to keep going on a time-out, unless the user does some action.
var inactivity = function () {
var t;
// user doing something on your page, so keep resetting time counter when events happen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
// this is a callback function that will get called once a time-out countdown is done
function timeOut() {
// make a request to '/logout' here and logout the current user (you still will have access to req.user from Passport)
// also can redirect from back-end route handler to the login page for instance
}
// this gets called whenever an event happens, resetting the counter of sorts
function resetTimer() {
t = 0;
t = setTimeout(timeOut, 1000 * 60 ) // set this to however long you should wait to log out your user time (in milliseconds)
}
};
So basically what this approach would let you do, is automatically invalidate sessions yourself, which means you would have much greater control over updating the state of your database and logging users out.
Hope this helps!

Node.js authentication using passport and typescript: cannot find req.user

I am currently working on a authentication service for a node.js microservices application using typescript, WebStorm, passport and jwt. While trying to add the route to "/api/login", I am noticing that the intellisense does not seem to pick up the user object of req.user or the authorization object of req.header.authorization. For example, the following method is not working because it can not find the user object:
private generateToken(req: Request, res: Response, next: NextFunction){
req.token = jwt.sign({
id: req.user.id,
firstname: req.user.firstname,
lastname: req.user.lastname,
roles: req.user.roles
}, process.env.AUTH_KEY, {
expiresIn: "7d"
});
return next();
}
I am using the Request object from express:
import { NextFunction, Request, Response, Router } from "express";
Would I need to use a different Request object?
Also, if I need to force authentication to certain api routes but lock other routes down, how should this be done using passport-jwt? I know there is an express-unless package that I can use for express-jwt.
Not sure why this question downvoted, maybe because it should be two separate questions.
You can extend type declarations for Express.
Extending the express type definitions
Add a file library-ext.d.ts into your source directory with this.
declare module 'express' {
export interface Request {
user?: any;
}
}
For req.header.authorization try: req.headers['authorization']. Notice the 's'.
Relating to general authentication
It depends whether your registered users can also use the guest routes. If you never need identity on the guest routes then just register the passport authentication middleware only on the authenticated routes, or split the routes into separate routers. That's fairly simple to do, just search stack overflow or look in the docs for it.
The more complicated case is when you need both authenticated and non-authenticated users to access a route - think either a guest or authenticated customer adding something to a cart. Unfortunately passport-jwt rejects with a 401 when a token is not in the authorzation header, so the easiest way I found, rather than forking the project or rolling my own strategy, was to use middleware to add a known value to represent an otherwise anonymous request. Then just make sure that middleware is before the passport authentication in the affected routes. Here's a snippet to get you going:
CoreCtrl
class CoreCtrl {
simulateAnonymous(req, res, next) {
if (!req.headers.authorization) {
req.headers.authorization = 'Bearer guest-token';
}
return next();
}
}
Then somewhere in your Express Setup
setupRouters() {
// the public and admin routers are bound to the application
const coreCtrl = new CoreCtrl(this.serverOpts);
const anonymousCtrl = coreCtrl.simulateAnonymous.bind(coreCtrl);
this.routers.admin.use(anonymousCtrl);
this.routers.admin.use(passport.authenticate('UserBearer', { session: false }));
this.routers.public.use(anonymousCtrl);
this.routers.public.use(passport.authenticate('CustomerBearer', { session: false }));
}
Note that I had separate routers for public and admin set up here, that's not necessary but just to illustrate how to do it.
Then in the bearer strategy, you would have some code similar to this.
/**
* Run the strategy
*
* #param token {String} The JWT Token
* #param done {Callback} Callback function
*/
exec(token:string, done):Promise<any> {
// this is the workaround to support not passing a token for guest users.
if (token === 'guest-token') {
return done(null, {
userId: 'guest',
roles: ['guest']
});
}
// otherwise decode the token and find the user.
}
Finally, in some later Middleware you can check if the 'guest' role has access to the protected resource. I'd recommend acl module to manage role-based ACL list.
Property 'isAuthenticated' does not exist on type 'Request'.
Google lands here for that search, and the solution is to use Request/Response types in Express. My code was defaulting to the Fetch API's Request/Response definitions.
import { Express, NextFunction, Request, Response } from 'express'

user and role authorization in swagger api express

guys
on my exisiting api i already have user authhication using Bearer security. Using http header api_key and later tokens.
My problem seems to be i have diffefrent end point that are only need to be consumed based on roles.
For example to post a new user :
POST user should only be authenticated to user with admin role.
I have looked at the swagger spec here but nothing i could find on thier docuemation and google as well.
Please could give me some brain stroming idea ? below is my access verifaction code in nodejs and express.
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
app.use(middleware.swaggerSecurity({
Bearer: function(req,def,apiKey,next){
apiKey= apiKey.slice(7)
debug("token check",def,apiKey)
var ok=checkToken(apiKey)
if(ok) {
req.user=ok
debug('Token is ok')
return next()
}
debug("Invalid token",apiKey)
var err=Error("Invalid token")
err.statusCode=403
next(err)
}
}));
As of this writing, the solution is still homebrewing. Swagger does not, save through oAuth scopes or using a "hacky" api-key security definition (https://stackoverflow.com/a/40222161/3736937), have a built in RBAC mechanism.
Fortunately, we can create some pretty basic middleware to handle the problem because swagger does allow us to add x-swagger-* members to the swagger definition.
So here's what I did:
Add x-swagger-roles to each endpoint that requires RBAC (Role-based Access Control)
paths:
"/":
x-swagger-router-controller: getStatus
get:
operationId: getStatus
x-swagger-roles:
- admin
tags:
- "users"
summary: "Returns message: 'working'"
description: "default endpoint for testing"
responses:
$ref: "#/definitions/AnyResponse"
Place middleware before swagger-node-runner is registered with the application. In our case we're using expressjs, so the connect middleware is used.
var findOne = function (haystack, arr) {
return arr.some(function (v) {
return haystack.indexOf(v) >= 0;
});
};
app.use(function(req, res, next) {
var operation = runner.getOperation(req);
if(operation && operation.definition) {
var definition = operation.definition;
var requiredRoles = definition['x-swagger-roles'];
// if the endpoint has no required roles then go to the next route
if(!requiredRoles) return next();
// get the users roles
var userRoles = req.session.roles; // this may differ for you
// if any roles match then go to the next route
if(findOne(userRoles, requiredRoles)) return next();
// if no roles match then assert that this endpoint is forbidden
else return res.sendStatus(403);
}
next();
})
// it's important to register the middleware after the role check
runner.expressMiddleware().register(app);
Notes:
This code has not been tested in production, and should be reviewed by a security professional.
x-swagger-roles will not appear in your swagger-ui without altering it, which is beyond the scope of this answer.

Get the state parameters from OAuth callback in passportjs

I am trying to send state parameters to the Oauth and then catch them in the callback, but I cannot make it work. So does passportjs support such a functionality?
My idea is to send an id as state parameter to the Oauth and then on the callback depending on the id from the state parameters sent back to my app I want to do a proper redirect.
In the Twitter strategy I have enabled
passReqToCallback: true,
state: true
My request should look like this:
app.get('/auth/twitter/:gameId/:url',
function(req, res){
try {
var json = JSON.stringify({gameId: req.params.gameId, url: req.params.url});
var encodedValues = base64url(json);
console.log('encodedValues:'+encodedValues)
passport.authenticate('twitter', {state:encodedValues})
}catch (e){
console.log(e);
}
}
);
then on the callback
app.get('/auth/twitter/callback', passport.authenticate('twitter', function(req, res, next) {
try{
//get the state params from the res uri/body
//decode the state params
//redirect depending on the state.gameId
}catch(e){
console.log('twitter exception:'+e);
}}));
I already know that I can save the id in a session, but I would like to know if there is a session less way to do it by passing this information from url since it is not sensitive.
Thanks
With oAuth 2.0 you don't have to rely on any type of session for this. The way you pass in state on the /auth/twitter/:gameId/:url route, you would be able to access the state on your callback with req.query.state.
In case someone else runs into problems, when trying to pass application state through the oAuth progress, this is how I solved it in my Node-app:
Since I want to redirect the user to different locations, based on the location the user came from before entering the authentication screen, I built different routes for accessing the authentication screen, and passed different state along depending on which route was hit. Like this:
router.get('/google/io', passport.authenticate('google', {
scope: ['email', 'profile'],
state: 'io'
}));
router.get('/google/de', passport.authenticate('google', {
scope: ['email', 'profile'],
state: 'de'
}));
So if my users access the auth screen coming from my page using the .io domain, my state parameter will carry the value of 'io', and if a user accesses the auth screen coming from the .de version of my page, the state parameter will carry the value of 'de'. On successful authentication, I can then just extract the value from req.query.state, and redirect them back to mypage.de or mypage.io

Resources