1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | 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}}
No comments:
Post a Comment