Use express-jwt as middleware to verify Azure AD issued tokens - node.js

I would like to know if its possible to use the express-jwt NPM package as middleware to verify JWT tokens issued by Azure AD.
We have a web API written in express/node and would like to apply middleware pattern to protect our endpoints and to populate the user principle.
seems like:
server.use(jwt({
audience: '{UUID}',
issuer: 'https://sts.windows.net/{UUID}',
}).unless({path : ['/']}))
does not work as it requires a client secret, but from AD (much like in implicit flow) the tokens are retrieved via a user interaction and there is no client secret.

You can use "azure-ad-jwt". Its fairly straight forward and requires no injection into the middleware. You can inject it as an intermediary step in your own "middleware" function of course.
private verifyToken(req: any, res: any) {
var audience = "xxxxxxxxx";
var tenantId = "xxxxxxxxx";
var authorization = req.headers['authorization'];
return Rx.Observable.create((observer) => {
if (authorization) {
var bearer = authorization.split(" ");
var jwtToken = bearer[1];
if (jwtToken) {
aad.verify(jwtToken, { audience: audience, tenantId: tenantId }, function (err, result) {
if (result) {
observer.next(true);
} else {
res.status(401).send('That is not a valid token!');
}
})
} else {
res.status(401).send('No token in header.');
}
} else {
res.status(401).send('Missing authorization attribute in header.');
}
});
}

express-jwt supports multi-tenancy, which works perfect with azure ad. https://github.com/auth0/express-jwt#multi-tenancy
import _ from 'lodash';
import jwt from 'express-jwt';
const ISS = 'https://sts.windows.net/********-****-****-****-************/';
const KEYS = {}; // response.body from https://login.microsoftonline.com/common/discovery/keys
// create another task to refresh KEYS every 24 hours.
app.use('/protected', jwt({
secret: (req, header, payload, done) => {
const { kid } = header;
const { iss } = payload;
if (iss !== ISS) { // issuer filter
return done(new Error('invalid token issuer'));
}
const key = _.find(KEYS, { kid });
if (key) {
const pem = _.get(key, 'x5c[0]');
if (pem) {
return done(null, `-----BEGIN CERTIFICATE-----\n${pem}\n-----END CERTIFICATE-----`);
}
return done(new Error('not PEM found'));
}
return done(new Error('invalid kid'));
},
}), (req, res) => {
res.json({ msg: 'if you can see it, you have a valid access_token.' });
});
app.use((err, req, res, next) => { // error handler
res.status(401).json({ msg: err.message });
});
References:
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims
https://stevelathrop.net/securing-a-node-js-rest-api-with-azure-ad-jwt-bearer-tokens/

Related

Why does req.params.id return "undefined"?

Learning about the concept of microservices in Nodejs, I have set up two microservices auth and users, both standalone and running on different ports.
The auth service handles user creation and log-in users using a username and password. This works as expected. I've used jwt to generate the tokens. I can easily create a user, create a session token and verify its validity.
My second service, users, I intend to use to show greetings to users and fetch a user's detail. I need to use the auth service to know when a user is logged in in this setting.
However, with my current workings, when I try to go to an endpoint, /users/:id/sayhello with a valid user id and a valid token passed in the headers, I get the following errors:
TypeError: Cannot read properties of undefined (reading 'id') at /path/auth/verifyToken.js:23:21 .
And then this; from jwt:
{
"name": "JsonWebTokenError",
"message": "secret or public key must be provided"
}
Let's look at my setup now.
Here's my verifyToken.js file from the auth service:
const verifyToken = (req, res, next)=>{
const authHeader = req.headers.token
// split the header, and get the token "Bearer token"
const token = authHeader.split(" ")[1];
if (authHeader) {
jwt.verify(token, process.env.JWT_SEC, (err, user)=>{
if (err) res.status(403).json(err);
req.user = user
next();
})
} else {
return res.status(401).json("You are not authenticated")
}
}
const verifyTokenAndAuthorization = (req, res, next) =>{
verifyToken(req, res, ()=>{
if(req.user.id === req.params.id){ // error traced back to this line
next();
}else{
res.status(403).json("Permission denied!");
}
})
}
From my users service, here's the code that uses the auth service to know when the user is logged in then say hello.
app.get("/users/:id/sayhello", verifyTokenAndAuthorization, async (req, res) => {
try {
const user = await User.findById(req.params.id);
console.log(req.params.id) // undefined
res.status(200).json(`Hello ${user.username}`);
} catch (error) {
res.status(500).json(error);
}
});
I've with no success sought any leads from similar posts like A,B and C
I'm not sure of what's not right. I'll appreciate possible suggestions and leads towards a fix.
console.log(process.env.JWT_SEC)
The authentication process got failed, so the user property was unset on the req object, so req.user is null.
Ensure the integrity of your inputs.
I think in the Headers convention of using Authorization or authorization key will not dissappoint as its the most preferred way of doing this have something like
I have done a rolebased approach in tackling this so check the implementation as of the question rolebased structure.
What to check for
Check if the Authorization header is available
If it does not exist just throw an exception or a response with some error
Check if the token is present in the Bearer token
If the token is not there just throw an exception to temnate the excecution
If the AuthHeader and token are present then now you can be certain that you have the token, thus you can just return the jwt.verify(...args:[])
Depending on validity everything here is on check
If the jwt is valid then the JWT payload is there thn we can pass it to the request object to carry it through to the other middlewares
If we want to now have Athorization then we override the next parameter with a function to execute on it`s behalf
From here now you can check on the user Roles and return next based on what permissions they have.
import RoleModel from "../models/RoleModel"
import UserModel from "../models/UserModel"
class AuthMiddleware {
constructor(role:typeof Model, user:typeof Model) {
this.role=role
this.user=user
}
verifyJwt = async (req, res, next) => {
try {
const AuthHeader = req.headers["authorization"]
if (!AuthHeader) {
return res.status(401).json("Please provide an auth token")
}
const token = AuthHeader.split(" ")[1]
if (!token) {
return res.status(401).json("Please provide an auth token")
}
return jwt.verify(token, SECRET_KEY, async (error, payload) => {
if (error) {
return res.status(401).redirect("/auth/login")
}
const decodedPayload = payload as JWTPayloadType
req.user = decodedPayload
return next()
})
} catch (error) {
return next(error)
}
}
loginRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.USER)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
adminRequired = async (req, res, next) => {
try {
this.verifyJwt(req, res, async () => {
const user = await this.user.findById(req.user.userId)
const role = await this.role.findById(user.role)
const permitted = await role.hasPermission(Permissions.ADMIN)
if (!permitted) {
return res.status(403).json("Forbidden")
}
return next()
})
} catch (error) {
return next(error)
}
}
}
export default new AuthMiddleware(RoleModel, UserModel)
Applying this to a middleware
import auth from "../middlewares/AuthMiddleware"
/**
* ************* UPDATE USER PROFILE ********
*/
router
.route("/update/profile/:id")
.put(
auth.loginRequired,
imageUpload.single("profile"),
uController.updateUserDetails,
uMiddleware.uploadProfilePic,
)
Assuming you supply the middlewares to the given route its easy to abstract away the verify jwt and have a login_required based on the roles you want achieved.
Full implementation of this I have on this Github repo Github link

How to solve JsonWebTokenError "invalid signature" after assigning some infos to the generated token?

Have a problem when trying to verify the token (it was working fine before i added some data to it before generating it) .. but now it does not seem to be working !
This is how i generate the token when user send a POST request (login)
require('dotenv')
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs')
const Role = require('../models/Role');
const Section = require('../models/Section');
const User = require('../models/User');
// Login !
router.post('/', async (req, res) => {
let sections_fetched = [];
// Validate data
// Check username
const user = await User.findOne({username: req.body.username });
if(!user) return res.status(400).send('Wrong user login credentials !');
// Check password
const is_pass_valid = await bcrypt.compare(req.body.password , user.password);
if (!is_pass_valid) return res.status(400).send('Wrong user login credentials !');
// Get role Object:
const _role = await Role.findOne({_id:user.role , is_deleted:false});
if (!_role) res.json("Failed fetching role !");
// loop through sections
for (let index = 0; index < _role.sections.length; index++) {
const tmpRole = await Section.find({_id: _role.sections[index], is_deleted:false});
sections_fetched.push({access:tmpRole[0].access , name:tmpRole[0].name});
}
// create jwt token
const token = jwt.sign({username:user.username, role:{name:_role.name, sections:sections_fetched}}, 'secret', {expiresIn : '24h'}, process.env.JWT_TOKEN_SECRET);
res.json({token:token});
});
this is my JWT verification middleare :
require('dotenv')
const jwt = require('jsonwebtoken');
module.exports = function (req, res, next) {
const token = req.header('auth-token');
if (!token) return res.status(401).send('Access Denied !');
console.log(process.env.JWT_TOKEN_SECRET);
console.log(token);
try
{
const verified = jwt.verify(token, process.env.JWT_TOKEN_SECRET);
req.user = verified;
next();
}
catch (error)
{
res.status(400).send('Invalid token !');
}
}
and this is a simple example of listing users (using the JWT verification middleware ! ) :
const verifyToken = require('../middlewares/verifyToken'); // my jwt middleware to verify !
// Listing All users
router.get('/', verifyToken, async (req, res) =>
{
try
{
const users = await User.find({is_deleted:false});
res.json(users);
}
catch (error)
{
console.log("err ->\n"+error);
res.json({message: error});
}
});
what is 'secret in the below line? seems you are adding a secret key twice, replace the hardcoded word 'secret' with token from env
const token = jwt.sign({username:user.username, role:{name:_role.name, sections:sections_fetched}}, 'secret', {expiresIn : '24h'}, process.env.JWT_TOKEN_SECRET);
send a bearer token and your middleware should like this
require('dotenv')
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1]; // Authorization: 'Bearer TOKEN'
if (!token) {
throw new Error('Authentication failed!');
}
const verified = jwt.verify(token, process.env.JWT_TOKEN_SECRET);
req.user = verified;
next();
} catch (err) {
res.status(400).send('Invalid token !');
}
};
Please make sure then when generating the token you pass a valid algorithm. I stored the algorithm in an environment variable but used none which is not a valid algorithm. Thus even though the token got created, I couldn't verify it using the same secret. Spent hours trying to fix this. I hope this was helpful :D
sign(payload, JWT_SECRET, {
algorithm: JWT_ALGORITHM,
expiresIn: '1d'
});
You described that you have changed the contents.
A signature represents the integrity of the contents, to ensure the content that was signed has not been modified, and you are modifying the contents making the signature invalid. This error you have is accurate for what you have done.
You must create a new signature when the signed contents are changed.
For Sign:
jwt.sign(
{
username:user.username,
role:{
name: _role.name,
sections: sections_fetched
}
}, 'secret', {
expiresIn : '24h'
}, process.env.JWT_TOKEN_SECRET,
function(err,res) {
if (err) {
callback(res);
} else {
callback(res);
}
}
);
To Verify:
jwt.verify(req,'SecretKey', function (err, res) {
if (err) {
callback(res);
} else {
callback(res);
}
});
Hlo
this error occur when secretkey does't match with the key thats provided during when you verifing token
you can check you token on jwt website thats help you to get error main reason

Create a token Authentication for my restfull api in NodeJS

How is the best way to create a token auth in nodejs for access to api by front-end application?
If you have to create a authentication token you can refer JWT-Token:
Token Creation:
const token = jwt.sign({username},'my_secret_key',{ expiresIn: 60*60*24 });
res.json({
"success":true,
"data":data,
"token":token
});
Token verification:
const jwt = require('jsonwebtoken');
module.exports = function checkToken(req, res, next) {
var token = req.headers['token'];
if(token) {
jwt.verify(token, 'my_secret_key',(err,decode)=>{
if(err) {
res.json({"status":500,
"message":"INVALID TOKEN",
"error":err.message
});
} else {
next();
}
})
} else {
res.json({"status":500,
"message":"NO TOKEN PROVIDE",
"error":"token must be provide in header for endpoint access"
});
}
}
You can refer link here

How to correctly use the authentication for nodeJs API using JWT and Passport?

I am using JWT-simple for authenticating my express routes.
server side:
var jwt = require('jwt-simple');
var bcrypt = require('bcrypt');
var passport = require('passport');
require('../passport')(passport);
/* Create an Account */
router.post('/signup', function (req, res, next) {
var verifyCode = Math.random().toString(36).slice(-8);
var userData = {
name: req.body.name,
email: req.body.email,
phone: req.body.contact,
password: req.body.password,
verify_code: verifyCode,
status: 0
};
loginService.createUser(userData, function (err, data) {
if (err) {
res.status(500).json({error: true, data: {message: err.message}});
} else {
var token = jwt.encode(data, "secret");
res.json({success: true, data: {token: 'JWT ' + token}});
}
});
});
/* GET the info of an API using the jwt token data */
router.get('/info', passport.authenticate('jwt', {session: false}), function (req, res, next) {
var token = tokenRetrive.getToken(req.headers);
if (token) {
var decoded = jwt.decode(token, configVar.config.secret);
UserService.getContentUserById(decoded.id, function (err, user) {
if (err) {
res.status(500).json({error: true, data: {message: err.message}});
} else {
if (!user) {
res.send({success: false, msg: 'Authentication failed. User not found.'});
} else {
if (!user) {
return res.status(403).send({success: false, msg: 'Authentication failed. User not found.'});
} else {
res.json({success: true, data: user.toJSON()});
}
}
}
});
} else {
return res.status(403).send({success: false, msg: 'No token provided.'});
}
});
client side
var signup = function(user) {
return $q(function(resolve, reject) {
$http.post(API_ENDPOINT.url + '/signup', user).then(function(result) {
if (result.data.success) {
storeUserCredentials(result.data.data.token);
resolve(result.data);
} else {
reject(result.data.msg);
}
});
});
};
function storeUserCredentials(token) {
window.localStorage.setItem(TOKEN_KEY, token);
var loggedIn_user_Data = jwt_decode(token);
$http.defaults.headers.common.Authorization = token;
}
Using REST client (POSTMAN) when I pass the header info to the API I use
API : localhost:8080/info
Key
Authorization
Content-Type
Value
JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYXR1bCIsImVtYWlsIjoidHJlZUB0cmVlLmNvbSIsInBob25lIjpudWxsLCJwYXNzZHJlc3MiOm51bGwsImNvdW50cnkiOm51bGwsInN0YXRlIjpudWxsLCJwaW5jb2RlIjpudWxsLCJvcmdfaWQiOjAsInJvbGVzIjpudWxsLCJjcmVhdGVfZGF0ZSI6IjIwMTctMDUtMThUMTk6NTE6MDYuMDAwWiIsImxhc3RfbG9naW4iOiIyMDE3LTA1LTE4VDE5OjUxOjA2LjAwMFoiLCJhdmF0YXJfdXJsIjpudWxsfQ.umxBRd2sazaADSDOW0e8rO5mKDpQYIK1hsaQMZriZFE
application/json
The above API gives me the data only if the correct token is passed and seems working fine.
However in client side I can get the token retrieve using jwt-decode, without the use of any secret in client side, what if the token is caught by middle man, How can the security be enhanced?
Is there something I am missing to have correct use of JWT for my node api routes?
Some places I see the Authorisation is passed as bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYXR1bCIsImVtYWlsIjoidHJlZUB0cmVlLmNvbSIsInBob25lIjpudWxsLCJwYXNzd29yZCI6IiQyYSQxMCRIQVJPTy5PUEdYWFBvVktXOVhmYnZldk
When I try to use bearer I get error to get the info after authenticating.
What is this bearer and JWT being passed in value to header?
I am using passport-jwt
var JwtStrategy = require('passport-jwt').Strategy;
To use JWT tokens, you have to use SSL (https). Without it, you won't have protection at all.
JWT tokens are signed (check the site). So if someone (middle man) try to change it, it will be invalidated.
JWT and Bearer are basic the same thing. They are just the auth scheme for the authorization header.
The 'JWT' auth scheme is the default of the passport-jwt.
If you want to change it, you can use a different jwtFromRequest value.
See:
new Strategy({ ... jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme('Bearer') ... }, verifyFunction)
Hope its clear.

Node.js JWT, get user_id from token

strong textI am building node.js + mongodb rest api. I use jwt user auth and I have a problem. I need to get details of authenticated user (user_id, name), think they can be obtained from token, but I dont know how to do this. How is it possible to do ?
UPDATED
I am doing a post request
router.route('/articles')
.post(function (req, res) {
var article= new Article();
article.user_id = ???; // here needs user_id
article.imdb_id = req.body.imdb_id;
article.title = req.body.title;
article.thumb = req.body.thumb;
article.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Added' });
});
});
I need to insert into articles collection authors id (user_id), but I dont know how to get the authenticated user_id.
Tried to do this:
var token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
req.decoded = decoded;
console.log(decoded);
next();
}
});
decoded returns all info about user (name, password, _id). Is it possible to get only user_id and name from here?
When you sign a JSON web token you can pass it a user object. Here you can store whatever user data you need. This object is then signed and encoded and set as the token. When you send a request to your API passing the JWT in the auth header your validation function should return this user object back to you if the JWT is valid.
I like to use the Hapi framework for creating my Restful APIs so I will give an example using Hapi.
In your server.js file you need to register the hapi-auth-jwt2 package:
server.register(require('hapi-auth-jwt2'), (err) => {
if (err) {
throw err;
}
server.auth.strategy('jwt', 'jwt', {
key: config.jwt.secret,
validateFunc: auth.validate,
verifyOptions: { algorithms: ['HS256'] }
});
server.auth.default('jwt');
});
Your validation function:
export default {
validate: (tokenObject, req, callback) => {
validateToken(tokenObject.user_id, (err, user) => {
if (err) {
callback(Boom.unauthorized('User is unauthorized.'), false);
} else {
req.user = user;
callback(null, true);
}
});
}
};
The validateToken function should take the user id that you got from the token and query for the user. If a user is found then you know the token is valid and you can return and store the rest of the user information.
To create a token I use "jsonwebtoken" package:
generateToken: (user_id, name, callback) => {
'use strict';
callback(null, JWT.sign({
user_id: user_id,
name: name
}, config.JWT.SECRET, {
expiresIn: 86400
}));
}
Let's say you need to verify if the token sent from user In the headers already In your Database or not (we're going to call it protect)
const {promisify} = require('util');
const jwt = require('jsonwebtoken');
const User = require('./../models/userModel');
...
exports.protect = catchAsync(async(req, res, next) => {
// 1) Getting token and check if it's there in headers
let token;
//authorization is the name of the header token
if (req.headers.authorization) {
token = req.headers.authorization;
}
if (!token) {
return next(new AppError('You are not logged in! Please Login To get Access.', 401));
}
// 2) Verification Token is a valid token
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
// WE CAN GET THE USER ID FROM DECODED
// 3) Check if user still exists not deleted
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next(new AppError('the user does not exist.', 401));
}else{
// WHAT EVER YOU WANT TO DO AFTER CHECKING USER FOUND IN DATABASE
})

Resources