--- title: '07 Basic Authentication and Authorisation with JSON Web Tokens' disqus: hackmd --- 07 Basic Authentication and Authorisation with JSON Web Tokens === <style> img{ /* border: 2px solid red; */ margin-left: auto; margin-right: auto; width: 90%; display: block; } </style> ## Table of Contents [TOC] Authentication and Authorisation --- - HTTP and HTTPS are stateless - strict auth would require users to reauth everytime they access a protected res - hence rely on token-based auth which is performed at the start - auth system issues signed auth token to end-user app - token appended to every req of client when protected res required - unique token verifies your identity and authorise you - __JSON Web Tokens (JWT)__ is a means of performing auth and authorisation ### JWT - jwt - JSON based open standard for creating access tokens that assert some number of claims - composed of - header - payload - signature that proves integrity of msg of receiving server - content inside jwt is digitally signed either using a secret with HMAC algo OR leveraging the public key infrastructure (PKI) model with priv/pub RSA config ![](https://i.imgur.com/LGBkRdn.png) - 3 parts envoded using base64 __separately__ ```javascript= const token = base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature) // example output eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI ``` Implementing JWT in NodeJS --- - install jsonwebtoken lib - `npm install jsonwebtoken --save` ### app.js ```javascript= // post endpt for above function app.post('/api/login',function(req,res){ var email=req.body.email; var password=req.body.password; user.loginUser(email,password,function(err,result){ if(!err){ res.send("{\"result\":\""+result +"\"}"); }else{ res.status(500); res.send(err.statusCode); } }); }); ``` ### config.js - to hold the secret key for our jwt ```javascript= var secret='s12xyz00'; //your own secret key module.exports.key=secret; ``` ### user.js ```javascript= // use jwt.sign func to generate json web token and return to caller upon successful verification of login credentials // user's role and id is also encoded into the jwt // if fails, return null val for token var config=require('../config.js'); var jwt=require('jsonwebtoken'); loginUser: function (email,password, callback) { var conn = db.getConnection(); conn.connect(function (err) { if (err) { console.log(err); return callback(err,null); } else { console.log("Connected!"); var sql = 'select * from user where email=? and password=?'; conn.query(sql, [email,password], function (err, result) { conn.end(); if (err) { console.log(err); return callback(err,null); } else { //console.log(config.key); var token=""; if(result.length==1){ token = jwt.sign({ id: result[0].userid, role: result[0].role }, config.key, { expiresIn: 86400 //expires in 24 hrs }); } return callback(null,token); } }); } }); } ``` ### Verifying the Token - verifyToken.js - need to authorise verified user through token when protected service called - jwt shld be sent in the authorisation header using the bearer schema - `Authorization: Bearer <token>` - create new folder `auth/` to hold middleware funcs to chekc for presence of token and verify its validity ```javascript= // verifyToken.js var jwt=require('jsonwebtoken'); var config=require('../config'); function verifyToken(req,res,next){ console.log(req.headers); var token=req.headers['authorization']; //retrieve authorization header’s content console.log(token); if(!token || !token.includes('Bearer')){ //process the token res.status(403); return res.send({auth:'false',message:'Not authorized!'}); }else{ token=token.split('Bearer ')[1]; //obtain the token’s value console.log(token); jwt.verify(token,config.key,function(err,decoded){//verify token if (err){ res.status(403); return res.end({auth:false,message:'Not authorized!'}); }else{ req.userid=decoded.userid; //decode the userid and store in req for use req.role=decoded.role; //decode the role and store in req for use next(); } }); } } module.exports=verifyToken; ``` - rmb to apply middleware in app.js! ```javascript= var verifyToken=require('../auth/verifyToken.js'); … app.post('/api/user',verifyToken,function(req,res){ …… }); ``` Practical Example --- ```javascript= // user.verify() add to user model verify: function(username, password, callback) { const query = "SELECT * FROM user WHERE username=? and password=?"; db.query(query, [username,password], (error, results) => { if (error) { callback(error, null); return; } if (results.length === 0) { callback(null, null); return; }else{ const user = results[0]; callback(null, user); } }); }, ``` ```javascript= // /login end pt in controller const jwt = require("jsonwebtoken"); const JWT_SECRET = require("../config.js"); app.post("/login/", (req, res) => { User.verify( req.body.username, req.body.password, (error, user) => { if (error) { next(error); return; } if (user === null) { res.status(401).send(); return; } const payload = { user_id: user.id }; jwt.sign(payload, JWT_SECRET, { algorithm: "HS256" }, (error, token) => { if (error) { console.log(error); res.status(401).send(); return; } res.status(200).send({ token: token, user_id: user.id }); }) }); }); ... ``` ```javascript= // isLoggedInMiddleware.js // in callback if verification successful, we store decoded token in req.decodedToken and invoke next() to pass the req and res to the next middleware // else return 401 unauthorised const jwt = require("jsonwebtoken"); const JWT_SECRET = require("../config.js"); var check = (req, res, next) => { const authHeader = req.headers.authorization; if (authHeader === null || authHeader === undefined || !authHeader.startsWith("Bearer ")) { res.status(401).send(); return; } const token = authHeader.replace("Bearer ", ""); jwt.verify(token, JWT_SECRET, { algorithms: ["HS256"] }, (error, decodedToken) => { if (error) { res.status(401).send(); return; } req.decodedToken = decodedToken; next(); }); }; module.exports=check; ``` ```javascript= // mount middlewares in controller const isLoggedInMiddleware = require("./isLoggedInMiddleware"); ... app.put("/users/:userID/", isLoggedInMiddleware, (req, res, next) => { ... app.post("/users/:userID/friends/:friendID", isLoggedInMiddleware, (req, res) => { ... app.delete("/users/:userID/friends/:friendID/", isLoggedInMiddleware, (req, res) => { ``` ### Adding Authorisation - we have authenticated, but anyone who is loggedin can freely update/delete users - only want logged in user to update/delete their own ```javascript= // add check after userID parsed to see if userid in decoded token matches the one in req params // app.js app.put("/users/:userID/", isLoggedInMiddleware, (req, res, next) => { const userID = parseInt(req.params.userID); if (isNaN(userID)) { res.status(400).send(); return; } // user ID in the request params should be the same as the logged in user ID if (userID !== req.decodedToken.user_id) { res.status(403).send(); return; } ... app.post("/users/:userID/friends/:friendID", isLoggedInMiddleware, (req, res) => { const userID = parseInt(req.params.userID); const friendID = parseInt(req.params.friendID); if (isNaN(userID) || isNaN(friendID)) { res.status(400).send(); return; } if (userID === friendID) { res.status(400).send(); return; } // user ID in the request params should be the same as the logged in user ID if (userID !== req.decodedToken.user_id) { res.status(403).send(); return; } ... app.delete("/users/:userID/friends/:friendID/", isLoggedInMiddleware, (req, res) => { const userID = parseInt(req.params.userID); const friendID = parseInt(req.params.friendID); if (isNaN(userID) || isNaN(friendID)) { res.status(400).send(); return; } // user ID in the request params should be the same as the logged in user ID if (userID !== req.decodedToken.user_id) { res.status(403).send(); return; } ... ``` ###### tags: `BED` `DISM` `School` `Notes`