I am new to the use of Nodejs but I confess that I am loving, I am using tokens Jwt for authentication of users, simple thing, they log and win a token. but I want to put permissions on the routes, I've been reading about express-jwt-permissions, but using it in my application I'm getting the following error at POSTMAN: UnauthorizedError: user object "user" was not found. Check your configuration.
customerRoutes.js
const router = require('express').Router();
var jwt = require('jsonwebtoken');
var guard = require('express-jwt-permissions')()
const customerController = require('../controllers/customerController');
function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers['authorization'];
// Check if bearer is undefined
if(typeof bearerHeader !== 'undefined') {
// Split at the space
const bearer = bearerHeader.split(' ');
// Get token from array
const bearerToken = bearer[1];
// Set the token
req.token = bearerToken;
// Next middleware
next();
} else {
// Forbidden
res.sendStatus(403);
}
}
router.post('/posts',verifyToken,guard.check('admin'),
customerController.posts);
module.exports = router;
this is how my token looks : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtaXNzaW9ucyI6WyJhZG1pbiJdLCJpYXQiOjE1Mzc0MDg0NzMsImV4cCI6MTUzNzQ0ODQ3M30.8l1vezWWz3Gmb5M3N0DgsmCl-nZHK2c3GP-dKzYDLRU
From the documentation of express-jwt-permissions
This middleware assumes you already have a JWT authentication middleware such as express-jwt.
And then tracing towards express-jwt documentation, with the first example:
var jwt = require('express-jwt');
app.get('/protected',
jwt({secret: 'shhhhhhared-secret'}),
function(req, res) {
if (!req.user.admin) return res.sendStatus(401);
res.sendStatus(200);
});
And associate with your encountered error, it's safe to say that express-jwt-permissions middleware is expecting a populated req.user field with the value of an object (then I actually checked their source code https://github.com/MichielDeMey/express-jwt-permissions/blob/master/index.js#L11 and proves that it is the case).
So my suggestion would be either use express-jwt which does the work for you and is known to work with express-jwt-permissions, or populate a req.user object within your verifyToken middleware by yourself (requires decoding of the JWT), something like:
// decode JWT
// get decoded token.permissions
req.user = {
permissions: token.permissions
};
Before invoking next() to the next middleware.
Note that you can also tweak express-jwt-permissions to expect a different field than user (but still need to live in the req namespace of express middleware life-cycle), as well as a different name than permissions field inside the token payload. See https://www.npmjs.com/package/express-jwt-permissions#configuration for details. But either way, the bottom line is you'll need to decode the token first.
Related
In my node project I have the following basic code to connect to Azure via a token. The login/logout works great together with our Azure:
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000;
const config = {
auth: {
clientId: "XXX",
authority: "https://login.microsoftonline.com/common",
clientSecret: "XXX"
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
const pca = new msal.ConfidentialClientApplication(config);
const app = express();
app.get('/', (req, res) => {
res.send("Login Logout");
});
app.get('/dashboard', (req, res) => {
// check here for valid token...
});
app.get('/login', (req, res) => {
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: "http://localhost:3000/redirect",
};
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
app.get('/logout', (req, res) => {
res.redirect('https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri=http://localhost:3000/');
});
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read"],
redirectUri: "http://localhost:3000/redirect",
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
console.log("\nResponse: \n:", response);
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
But how to properly check after that logging in if the token is still valid?
So the question is, how can I be save that the user on /dashboard has still a valid token or is logged in?
app.get('/dashboard', (req, res) => {
// check here for valid token...
});
At the end I need a node.js application that:
is safe (token-based)
has user auth (msal)
can give granular permissions on routes
Can I do all that in node.js or better doing that in client-side? But am I then reducing the security?
Once you get your authentication result the first time, this should have received tokens if authentication was successful. You should be able to parse the Id token to get information about the user. You can then use that information to create a session via the web framework that you are using. The session can be used thorough out the web app to give you information like if the user is authenticated or not, how long they are authenticated for, and what they have permission to access. Usually web frameworks will create a cookie with a session id so that requests coming in will be able to have session information, and the user won't have to authenticate every time.
If the session expires, you can try acquiring a token silently (without prompting the user) by using the token cache that is part of MSAL. When you call acquire token silent, MSAL will automatically check if the access token is valid, if not it will try to refresh the access token via the refresh token. If neither are valid, they will return an error. At this point you can fall back to prompting the user again to authenticate (via the code that you have already shared).
I found a kind of dirty way to solve my issue. Could you maybe tell me if that is a proper way? Also my solution is not safe as the user could change the client-side JS code and ignore the user auth.
Create HTML file for /dashboard:
app.get('/dashboard', function(req, res) {
res.sendFile(__dirname + "/" + "index.html");
});
and here using this JS code:
var headers = new Headers();
var bearer = "Bearer " + "ey...........Ac"; // <---- accessToken
headers.append("Authorization", bearer);
var options = {
method: "GET",
headers: headers
};
var graphEndpoint = "https://graph.microsoft.com/v1.0/me";
fetch(graphEndpoint, options)
.then(resp => {
// when error redirect to ... otherwise do log:
console.log(123);
});
on the <---- accessToken putting a valid token and it works. Only when token is valid the console.log is done.
So this works, but as I said, if the user is manipulating the code he can still see the page. Also the page is loading until the script is fired. So I cannot see a real value on this. This should happen on server-side somehow. Any idea?
Using the silent-flow was a good idea. It works great on my example.
https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-node-samples/standalone-samples/silent-flow
This works with the PublicClientApplication and acquireTokenSilent works also as expected.
i was wandering what is the best way to avoid sending the user data on every request,
lets say i want to add product from user's account, i have to send the user. or i want to order something, i have to send the user.
i thought about something like this:
app.use(async (req, res, next) => {
if (!req.body.userId) {
return next();
}
const user = await enteties.User.findByPk(req.body.userId);
req.user = user;
next();
});
but it also requires me to send the user on evey request..
there must be a way to avoid sending the user data to the server on almost every request.
also, it will make all my requests of type "post" since i have to send the user, and even "get" requests are now become "posts", for sure this is not correct
If you implement your JWT token correctly you don't need to send the logged in user id.
JWT tokens contain a payload section that is basically any JSON data you want to set. This is basically your decentralized session stored in the user's machine. When creating a JWT token you'd normally do something like:
const jwt = require('jsonwebtoken');
const config = require('./config');
function generateToken(user) {
let payload = {
sub: user.id
};
return jwt.sign(payload, config.secret, {
algorithm: 'HS512', // choose algorithm appropriate for you
expiresIn: config.expires
})
}
That payload part allows you to send user identifying information. In the case above, the user id. To get that id from a request simply verify it:
app.use((req, res, next) => {
const token = req.get('Authorization');
jwt.verify(token, config.secret, (err, payload) => {
if (err) {
next(err);
}
else {
req.user = payload; // user.sub is the user id
next();
}
});
});
Or you can use a library such as express-jwt to do it for you:
const expressJwt = require('express-jwt');
const express = require('express');
const config = require('./config');
const app = express();
app.use(expressJwt({ secret: config.secret }); // use express-jwt like any
// middleware, you can even install
// it on specific routes.
Now in your controller/route you can simply extract the payload in the req.user object. Invalid tokens or requests without tokens will completely skip your handler and immediately return an error or unauthorized response:
app.get('/some/endpoint', (req, res) => {
console.log('user is', req.user.sub); // note: req.user is our payload
});
Additional tricks:
As I mentioned, the payload is basically user defined. If you need to keep track of other user information such as roles or permissions you can store them in the JWT token:
// Example payload
let payload = {
sub: user.id,
admin: user.role === 'admin',
gender: user.gender
};
This reduces the number of database requests needed to process the user. Making the authentication system completely decentralized. For example you may have a service that consumes this JWT token that is not connected to your user database but need to check if user is admin. With the right payload that service does not even need to have access to the user database.
Note however that the payload is not encrypted. It is just base64 encoded. This means that the information in the token can be easily read by anyone with access to it (normally the user but beware of 3rd party scripts). So ideally you shouldn't store dox-able information in the payload if you have 3rd party scripts on your website (then again, it is highly unusual these days for anyone to write the entire front-end from scratch without any libraries or frameworks)
Also note that the more you put in your payload the larger your token will be.
I'm trying to add a new route to fetch a user by id but my error handling is not working correctly. Here is the code for that route.
const express = require('express');
require('./db/mongoose');
const User = require('./models/user');
const Task = require('./models/task');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
// ***removed code for brevity
// Route for fetching user by id
app.get('/users/:id', (req, res) => {
//console.log(req.params.id);
const _id = req.params.id;
User.findById(_id)
.then(user => {
//console.log(user)
if (!user) {
return res.status(404).send();
}
res.send(user);
})
.catch(e => {
res.status(500).send();
});
});
So if I test the route on Postman and I enter the correct user id from the database I get that user sent back, which is the the correct response. But if I enter an incorrect user id I get the 500 error code response instead of the 404 error code. The if (!user) statement is getting skipped and I can't figure out why. Any thoughts as to what I am missing?
Running this thru my own personal mongoose/express-using project, I get the following error:
UnhandledPromiseRejectionWarning: CastError: Cast to ObjectId failed for value "12345" at path "_id" for model "User"
That basically means Mongoose is expecting its own specific object type, an "ObjectId". This is a bit of a pain, since normally if you're using .findOne({_id:something), you can just use a string. If we do:
User.findById(mongoose.Types.ObjectId(_id))
it should work. Note that if you use an invalid id (like I obviously did here, it'll still error out. For that reason, I'd use the standard NodeJS format for callbacky stuff:
.then((err,result)=>{
//other stuff
});
In general, the .catch() block should only happen if obviously Mongoose and your router can't handle it.
EDIT: Also, for others info, Mongoose.model.findById is a built-in convenience method, and should basically do exactly what it says on the tin.
I am using webook to call one of my routers in my node js application.
I want to not use 'csurf' which is a library for CSRF token when that specific router is called.
The router is called ch and it is not the index router
My code for the CSRF expection is like this
var csrf = require('csurf');
var csrfProtection = csrf();
var csrfExclusion = ['/check-ch'];
var conditionalCSRF = function (req, res, next) {
if(csrfExclusion.indexOf(req.path) !== -1){
next();
} else {
csrf(req, res, next);
next();
}
}
router.use(conditionalCSRF);
router.post('/check-ch',(req, res, next) => {
console.log(req.body);
console.log('i am here');
});
Normally I searched stackoverflow but all answer shows that the method above should work but in my terminal I end up getting invalid csrf token, and I am not sure why.
The CSRF token is also being used in the index.js router, by the same method, where I load it first and then directly use router.use(csrfProtection), no conditionalCSRF is being used.
It looks like the conditionalCSRF is not even getting called, when I try to use a webook, so I am not sure what is stopping it to run.
There is a passport.js implementation which is being used for LDAP-auth which works. Now the next step is to encrypt the password on the client-side using Crypto-js as follows:
Client-side angular-js controller
$scope.authenticate = function () {
var auth = new login();
auth.username = $scope.username;
auth.password = CryptoJS.AES.encrypt($scope.password); //// HERE
auth.$save(function (response){
console.log(response);
},function(err){
console.log(err);
});
}
Server-side service
.....
.....
app.post('/login', passport.authenticate('ldapauth'), (req, res) => {
console.log("req.user: ",req.user);
req.session.username = req.user[ldap.username];
req.session.userModel = req.user;
res.status(200).send({"success": 'success'});
});
.....
On the server-side service before calling passport.authenticate with the request 'req' the aes encrypted password needs to be decrypted. How can that be implemented here? (The question is not about encryption but how to get data before it gets passed to passport.authenticate as request)
#Abhijay Ghildyal I don't think they understood your question. It is indeed possible to intercept the request before it gets passed to passport.authenticate(). What you'd want to do is to add this passage of code to your express.js or whichever file you did your express server implementation in. Also I am decrypting the request.body here instead of req.user since at that point of time the user is not yet logged in, however if it's different in your case then that's fine you can decrypt req.user the same way. (The variable app here is the name of your server i.e var app = express();)
app.use(function(req, res, next) {
if(req.url === '/login'){
//CryptoJS.AES.decrypt() is Assumed to be the decrypter function here.
req.body = CryptoJS.AES.decrypt(req.body);
console.log(req.body); //To view decrypted body
}
next();
});
That is it. This middleware function will be reached first before the passport.authenticate() function. Just make sure if you're applying this to req.body you add these lines of codes first, after importing the bodyParser (bodyParser = require('body-parser');) before the passage above.
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());