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.listen(8080);
Do login:
$ 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: 465 ETag: W/"1d1-fL53kZkGhDz1fE7e8SWj1fsPmqk" Date: Thu, 04 Jul 2019 05:34:50 GMT Connection: keep-alive {"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjE4NDkwLCJleHAiOjE1NjIyMTg1NTB9.BJzSGzbVgy_WZJAABoQ_xCPgf6OZgiezn7KxAiVrkm4","refreshToken":"KPImAwtuR4KMlU6RA7cdoouBJ3JY2XxiRzd4hTu3gU"}
Test authentication middleware:
$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjE4NDkwLCJleHAiOjE1NjIyMTg1NTB9.BJzSGzbVgy_WZJAABoQ_xCPgf6OZgiezn7KxAiVrkm4" \ -H "Content-Type: application/json" \ --request POST --data '{"guestsCount": 7}' \ http://localhost:8080/restaurant-reservation
Output:
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 94 ETag: W/"5e-IgmolWlx16EymTgKApJWpv27g74" Date: Thu, 04 Jul 2019 05:35:38 GMT Connection: keep-alive {"user":{"username":"linus","role":"admin","iat":1562218490,"exp":1562218550},"guestsCount":7}
Do again the command above after 60 seconds. Output:
HTTP/1.1 401 Unauthorized X-Powered-By: Express Date: Thu, 04 Jul 2019 05:36:24 GMT Connection: keep-alive Content-Length: 12 Unauthorized
As the existing access token expired, we need to get new access token using the refresh token:
$ curl -i -H "Content-Type: application/json" \ --request POST \ --data '{"username": "linus", "refreshToken": "KPImAwtuR4KMlU6RA7cdoouBJ3JY2XxiRzd4hTu3gU"}' \ http://localhost:8080/token
Output:
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 191 ETag: W/"bf-is3S3uVXUUs7vW0DjF+cQ+bzj70" Date: Thu, 04 Jul 2019 05:37:56 GMT Connection: keep-alive {"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjE4Njc2LCJleHAiOjE1NjIyMTg3MzZ9.bsy2oAm8qU8R6xM5b4SpxqJm8l8Ca4ssRu6VMMtZZq4"}
Test authentication middleware using the new access token:
$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImxpbnVzIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTYyMjE4NzY0LCJleHAiOjE1NjIyMTg4MjR9.Fw_HFTJmTc-dOjzExoJF_Wk2QGwYINaG_d6cuoc6fjQ" \ -H "Content-Type: application/json" \ --request POST --data '{"guestsCount": 7}' \ http://localhost:8080/restaurant-reservation
Output:
HTTP/1.1 200 OK X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 94 ETag: W/"5e-E8689sgC1zfAYBFoQnj/jmIPd+4" Date: Thu, 04 Jul 2019 05:39:54 GMT Connection: keep-alive {"user":{"username":"linus","role":"admin","iat":1562218764,"exp":1562218824},"guestsCount":7}
If we need the user to re-login, just revoke (delete) the refresh token:
$ curl -i -X DELETE http://localhost:8080/token/KPImAwtuR4KMlU6RA7cdoouBJ3JY2XxiRzd4hTu3gU
Output:
HTTP/1.1 204 No Content X-Powered-By: Express ETag: W/"a-bAsFyilMr4Ra1hIU5PyoyFRunpI" Date: Thu, 04 Jul 2019 05:43:43 GMT Connection: keep-alive
Test getting new access token:
$ curl -i -H "Content-Type: application/json" \ --request POST \ --data '{"username": "linus", "refreshToken": "KPImAwtuR4KMlU6RA7cdoouBJ3JY2XxiRzd4hTu3gU"}' \ http://localhost:8080/token
Output:
HTTP/1.1 401 Unauthorized X-Powered-By: Express Content-Type: text/plain; charset=utf-8 Content-Length: 12 ETag: W/"c-dAuDFQrdjS3hezqxDTNgW7AOlYk" Date: Thu, 04 Jul 2019 05:44:24 GMT Connection: keep-alive
Related code, roles authorization: https://www.anicehumble.com/2019/07/session-less-authorization-with-passport-authorization-header-jwt.html
No comments:
Post a Comment