const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const randtoken = require('rand-token');
const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const refreshTokens = {};
const SECRET = "sauce";
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: SECRET
};
passport.use(new JwtStrategy(options, (jwtPayload, done) =>
{
const expirationDate = new Date(jwtPayload.exp * 1000);
if (new Date() >= expirationDate) {
return done(null, false);
}
const user = jwtPayload;
done(null, user);
}));
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(passport.initialize());
app.post('/login', (req, res, next) =>
{
const { username, password } = req.body;
const accessToken = generateAccessToken(username, getRole(username));
const refreshToken = randtoken.uid(42);
refreshTokens[refreshToken] = username;
res.json({accessToken, refreshToken});
});
function generateAccessToken(username, role, expiresInSeconds = 60)
{
const user = {
username,
role
};
const accessToken = jwt.sign(user, SECRET, { expiresIn: expiresInSeconds });
return accessToken;
}
function getRole(username)
{
switch (username) {
case 'linus':
return 'admin';
default:
return 'user';
}
}
app.post('/token', (req, res) =>
{
const { username, refreshToken } = req.body;
if (refreshToken in refreshTokens && refreshTokens[refreshToken] === username) {
const accessToken = generateAccessToken(username, getRole(username));
res.json({accessToken});
}
else {
res.sendStatus(401);
}
});
app.delete('/token/:refreshToken', (req, res, next) =>
{
const { refreshToken } = req.params;
if (refreshToken in refreshTokens) {
delete refreshTokens[refreshToken];
}
res.send(204);
});
app.post('/restaurant-reservation', passport.authenticate('jwt', {session: false}), (req, res) =>
{
const { user } = req;
const { guestsCount } = req.body;
res.json({user, guestsCount});
});
app.get('/user-accessible', authorize(), (req, res) =>
{
res.json({message: 'for all users', user: req.user});
});
app.get('/admin-accessible', authorize('admin'), (req,res) =>
{
res.json({message: 'for admins only', user: req.user});
});
function authorize(roles = [])
{
if (typeof roles === 'string') {
roles = [roles];
}
return [
passport.authenticate('jwt', {session: false}),
(req, res, next) =>
{
if (roles.length > 0 && !roles.includes(req.user.role)) {
return res.status(403).json({message: 'No access'});
};
return next();
}
];
}
app.listen(8080);
Test login for non-admin:
$ curl -i -H "Content-Type: application/json" --request POST --data '{"username": "richard", "password": "stallman"}' http://localhost:8080/login
Output:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 253
ETag: W/"fd-eFe0WyxYJMwm+IYU2RknNmwLN7c"
Date: Thu, 04 Jul 2019 11:18:34 GMT
Connection: keep-alive
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJpY2hhcmQiLCJyb2xlIjoidXNlciIsImlhdCI6MTU2MjIzOTExNCwiZXhwIjoxNTYyMjM5MTc0fQ.eVMIVLoq66wwnvX7R7_YE_Va5uUIupcWqZFJIql2VOo","refreshToken":"ZExtyNqF19XwCF8htNABs9rzwDV5lltlEQxGAGVaeV"}
Test non-admin role against user-accessible resource:
$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJpY2hhcmQiLCJyb2xlIjoidXNlciIsImlhdCI6MTU2MjIzOTExNCwiZXhwIjoxNTYyMjM5MTc0fQ.eVMIVLoq66wwnvX7R7_YE_Va5uUIupcWqZFJIql2VOo" --request GET http://localhost:8080/user-accessible
Output:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 103
ETag: W/"67-cGYUdSCA9Hfxbr3kmo6EfcwJGFk"
Date: Thu, 04 Jul 2019 11:19:09 GMT
Connection: keep-alive
{"message":"for all users","user":{"username":"richard","role":"user","iat":1562239114,"exp":1562239174}}
Test non-admin role against admin-accessible resource:
$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJpY2hhcmQiLCJyb2xlIjoidXNlciIsImlhdCI6MTU2MjIzOTExNCwiZXhwIjoxNTYyMjM5MTc0fQ.eVMIVLoq66wwnvX7R7_YE_Va5uUIupcWqZFJIql2VOo" --request GET http://localhost:8080/admin-accessible
Output:
HTTP/1.1 403 Forbidden
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 23
ETag: W/"17-wr3eIgD4+9Bp6mVHZCD0DXWfISk"
Date: Thu, 04 Jul 2019 11:19:16 GMT
Connection: keep-alive
{"message":"No access"}
Test login for admin:
$ curl -i -H "Content-Type: application/json" --request POST --data '{"username": "linus", "password": "torvalds"}' http://localhost:8080/login
Output:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 251
ETag: W/"fb-ffoTYONCm1BVpI+gaFqCXe7AX2g"
Date: Thu, 04 Jul 2019 11:23:05 GMT
Connection: keep-alive
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjM5Mzg1LCJleHAiOjE1NjIyMzk0NDV9.hU09-ESu5VADYgC12R-CBtLga4lmGnpGC1AAwxg7t_Y","refreshToken":"8RItXk4v6kV8W68paZk3av34vj3oV5z1vnQdSLAZG7"}
Test admin role against admin-accessible resource:
$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjM5Mzg1LCJleHAiOjE1NjIyMzk0NDV9.hU09-ESu5VADYgC12R-CBtLga4lmGnpGC1AAwxg7t_Y" --request GET http://localhost:8080/admin-accessible
Output:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 104
ETag: W/"68-MXNiAugqialVCopW9uv3AMKWHjU"
Date: Thu, 04 Jul 2019 11:23:42 GMT
Connection: keep-alive
{"message":"for admins only","user":{"username":"linus","role":"admin","iat":1562239385,"exp":1562239445}}
And of course user-accessible resource is available to admin role too:
$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjM5Mzg1LCJleHAiOjE1NjIyMzk0NDV9.hU09-ESu5VADYgC12R-CBtLga4lmGnpGC1AAwxg7t_Y" --request GET http://localhost:8080/user-accessible
Output:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 102
ETag: W/"66-fLBX3f6AfyVZNVcfBNUakwJko5w"
Date: Thu, 04 Jul 2019 11:23:51 GMT
Connection: keep-alive
{"message":"for all users","user":{"username":"linus","role":"admin","iat":1562239385,"exp":1562239445}}