├── .gitignore ├── README.md ├── api ├── controllers │ └── main-controller.js ├── helpers │ └── auth.js └── swagger │ └── swagger.yaml ├── app.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Swagger JWT Example 2 | 3 | Minimal working example of how to integrate JWT with Swagger using Node.js. You can read the full post on my blog: [http://miguelduarte.pt/2017/04/19/using-jwt-authentication-with-swagger-and-node-js/](http://miguelduarte.pt/2017/04/19/using-jwt-authentication-with-swagger-and-node-js/) 4 | 5 | ## How to run this 6 | 7 | 1) Start the server by running `npm start` 8 | 9 | 2) Check the swagger-ui on `http://localhost:3000/docs` 10 | 11 | 3) GET `http://localhost:3000/api/unprotected` should work 12 | 13 | 4) GET `http://localhost:3000/api/protected` should NOT work 14 | 15 | 5) POST `http://localhost:3000/api/login/user` with the following body 16 | `` 17 | { 18 | "username": "username", 19 | "password": "password" 20 | } 21 | `` 22 | and take the token that you get in the response 23 | 24 | 6) GET `http://localhost:3000/api/protected` again with the following header 25 | ``Authorization: Bearer _TOKEN_``, replacing `_TOKEN_ ` with the value you got from request #4 26 | 27 | Then you can try logging in as an admin and accessing the admin-only route. 28 | -------------------------------------------------------------------------------- /api/controllers/main-controller.js: -------------------------------------------------------------------------------- 1 | var auth = require("../helpers/auth"); 2 | 3 | exports.unprotectedGet = function(args, res, next) { 4 | var response = { message: "My resource!" }; 5 | res.writeHead(200, { "Content-Type": "application/json" }); 6 | return res.end(JSON.stringify(response)); 7 | }; 8 | 9 | exports.protectedGet = function(args, res, next) { 10 | var response = { message: "My protected resource for admins and users!" }; 11 | res.writeHead(200, { "Content-Type": "application/json" }); 12 | return res.end(JSON.stringify(response)); 13 | }; 14 | 15 | exports.protected2Get = function(args, res, next) { 16 | var response = { message: "My protected resource for admins!" }; 17 | res.writeHead(200, { "Content-Type": "application/json" }); 18 | return res.end(JSON.stringify(response)); 19 | }; 20 | 21 | exports.loginPost = function(args, res, next) { 22 | var role = args.swagger.params.role.value; 23 | var username = args.body.username; 24 | var password = args.body.password; 25 | 26 | if (role != "user" && role != "admin") { 27 | var response = { message: 'Error: Role must be either "admin" or "user"' }; 28 | res.writeHead(400, { "Content-Type": "application/json" }); 29 | return res.end(JSON.stringify(response)); 30 | } 31 | 32 | if (username == "username" && password == "password" && role) { 33 | var tokenString = auth.issueToken(username, role); 34 | var response = { token: tokenString }; 35 | res.writeHead(200, { "Content-Type": "application/json" }); 36 | return res.end(JSON.stringify(response)); 37 | } else { 38 | var response = { message: "Error: Credentials incorrect" }; 39 | res.writeHead(403, { "Content-Type": "application/json" }); 40 | return res.end(JSON.stringify(response)); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /api/helpers/auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var jwt = require("jsonwebtoken"); 4 | var sharedSecret = "shh"; 5 | var issuer = "my-awesome-website.com"; 6 | 7 | //Here we setup the security checks for the endpoints 8 | //that need it (in our case, only /protected). This 9 | //function will be called every time a request to a protected 10 | //endpoint is received 11 | exports.verifyToken = function(req, authOrSecDef, token, callback) { 12 | //these are the scopes/roles defined for the current endpoint 13 | var currentScopes = req.swagger.operation["x-security-scopes"]; 14 | 15 | function sendError() { 16 | return req.res.status(403).json({ message: "Error: Access Denied" }); 17 | } 18 | 19 | //validate the 'Authorization' header. it should have the following format: 20 | //'Bearer tokenString' 21 | if (token && token.indexOf("Bearer ") == 0) { 22 | var tokenString = token.split(" ")[1]; 23 | 24 | jwt.verify(tokenString, sharedSecret, function( 25 | verificationError, 26 | decodedToken 27 | ) { 28 | //check if the JWT was verified correctly 29 | if ( 30 | verificationError == null && 31 | Array.isArray(currentScopes) && 32 | decodedToken && 33 | decodedToken.role 34 | ) { 35 | // check if the role is valid for this endpoint 36 | var roleMatch = currentScopes.indexOf(decodedToken.role) !== -1; 37 | // check if the issuer matches 38 | var issuerMatch = decodedToken.iss == issuer; 39 | 40 | // you can add more verification checks for the 41 | // token here if necessary, such as checking if 42 | // the username belongs to an active user 43 | 44 | if (roleMatch && issuerMatch) { 45 | //add the token to the request so that we 46 | //can access it in the endpoint code if necessary 47 | req.auth = decodedToken; 48 | //if there is no error, just return null in the callback 49 | return callback(null); 50 | } else { 51 | //return the error in the callback if there is one 52 | return callback(sendError()); 53 | } 54 | } else { 55 | //return the error in the callback if the JWT was not verified 56 | return callback(sendError()); 57 | } 58 | }); 59 | } else { 60 | //return the error in the callback if the Authorization header doesn't have the correct format 61 | return callback(sendError()); 62 | } 63 | }; 64 | 65 | exports.issueToken = function(username, role) { 66 | var token = jwt.sign( 67 | { 68 | sub: username, 69 | iss: issuer, 70 | role: role 71 | }, 72 | sharedSecret 73 | ); 74 | return token; 75 | }; 76 | -------------------------------------------------------------------------------- /api/swagger/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: "0.0.1" 4 | title: Swagger JWT Example 5 | host: localhost:3000 6 | basePath: /api 7 | schemes: 8 | - http 9 | - https 10 | securityDefinitions: 11 | Bearer: 12 | type: apiKey 13 | name: Authorization 14 | in: header 15 | description: | 16 | For accessing the API a valid JWT token must be passed in all the queries in 17 | the 'Authorization' header. 18 | 19 | 20 | A valid JWT token is generated by the API and retourned as answer of a call 21 | to the route /login giving a valid user & password. 22 | 23 | 24 | The following syntax must be used in the 'Authorization' header : 25 | 26 | Bearer xxxxxx.yyyyyyy.zzzzzz 27 | 28 | consumes: 29 | - application/json 30 | produces: 31 | - application/json 32 | paths: 33 | 34 | /login/{role}: 35 | x-swagger-router-controller: main-controller 36 | post: 37 | operationId: loginPost 38 | description: "Login with a particular role" 39 | parameters: 40 | - name: role 41 | in: path 42 | required: true 43 | type: string 44 | - name: authentication 45 | in: body 46 | required: true 47 | schema: 48 | $ref: "#/definitions/Authentication" 49 | responses: 50 | "200": 51 | description: "Success" 52 | schema: 53 | $ref: "#/definitions/Token" 54 | "403": 55 | description: "Access Denied" 56 | schema: 57 | $ref: "#/definitions/Error" 58 | 59 | /unprotected: 60 | x-swagger-router-controller: main-controller 61 | get: 62 | operationId: unprotectedGet 63 | description: "Unprotected endpoint" 64 | responses: 65 | "200": 66 | description: "Success" 67 | schema: 68 | $ref: "#/definitions/Resource" 69 | 70 | /protected: 71 | x-swagger-router-controller: main-controller 72 | get: 73 | operationId: protectedGet 74 | description: "Protected endpoint, only accessible to 'admins' and 'users'" 75 | security: 76 | - Bearer: [] 77 | x-security-scopes: 78 | - admin 79 | - user 80 | responses: 81 | "200": 82 | description: "Success" 83 | schema: 84 | $ref: "#/definitions/Resource" 85 | "403": 86 | description: "Access Denied" 87 | schema: 88 | $ref: "#/definitions/Error" 89 | /protected2: 90 | x-swagger-router-controller: main-controller 91 | get: 92 | operationId: protected2Get 93 | description: "Protected endpoint, only accessible to 'admins'" 94 | security: 95 | - Bearer: [] 96 | x-security-scopes: 97 | - admin 98 | responses: 99 | "200": 100 | description: "Success" 101 | schema: 102 | $ref: "#/definitions/Resource" 103 | "403": 104 | description: "Access Denied" 105 | schema: 106 | $ref: "#/definitions/Error" 107 | 108 | definitions: 109 | Authentication: 110 | type: object 111 | properties: 112 | username: 113 | type: string 114 | example: "username" 115 | password: 116 | type: string 117 | example: "password" 118 | Resource: 119 | type: object 120 | properties: 121 | resource: 122 | type: string 123 | Token: 124 | type: object 125 | properties: 126 | token: 127 | type: string 128 | Error: 129 | type: object 130 | properties: 131 | message: 132 | type: string 133 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var app = require("express")(); 4 | var swaggerTools = require("swagger-tools"); 5 | var YAML = require("yamljs"); 6 | var auth = require("./api/helpers/auth"); 7 | var swaggerConfig = YAML.load("./api/swagger/swagger.yaml"); 8 | 9 | swaggerTools.initializeMiddleware(swaggerConfig, function(middleware) { 10 | //Serves the Swagger UI on /docs 11 | app.use(middleware.swaggerMetadata()); // needs to go BEFORE swaggerSecurity 12 | 13 | app.use( 14 | middleware.swaggerSecurity({ 15 | //manage token function in the 'auth' module 16 | Bearer: auth.verifyToken 17 | }) 18 | ); 19 | 20 | var routerConfig = { 21 | controllers: "./api/controllers", 22 | useStubs: false 23 | }; 24 | 25 | app.use(middleware.swaggerRouter(routerConfig)); 26 | 27 | app.use(middleware.swaggerUi()); 28 | 29 | app.listen(3000, function() { 30 | console.log("Started server on port 3000"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-jwt-example", 3 | "version": "0.0.1", 4 | "author": "Miguel Duarte", 5 | "contributors": [ 6 | "Miguel Duarte " 7 | ], 8 | "description": "Swagger JWT example", 9 | "keywords": [], 10 | "license": "", 11 | "main": "app", 12 | "dependencies": { 13 | "bcrypt-nodejs": "0.0.3", 14 | "express": "4.12.3", 15 | "jsonwebtoken": "7.3.0", 16 | "swagger-tools": "0.10.1", 17 | "validator": "7.0.0", 18 | "yamljs": "0.2.9" 19 | }, 20 | "engines": { 21 | "node": "7.7.0" 22 | }, 23 | "scripts": { 24 | "start": "node app" 25 | } 26 | } 27 | --------------------------------------------------------------------------------