Original text: jasonwatmore.com/post/2018/1…
In this tutorial, we will complete a simple example of how to implement role-based authorization/access using JavaScript in Node.js in conjunction with JWT authentication.
The example API has only three routes to demonstrate authentication and role-based authorization:
/users/authenticate
– Accepts a public route to the HTTP POST request that contains the username and password in the body. If the username and password are correct, a JWT authentication token is returned/users
– Only a secure route for user Admin and accepts HTTP GET requests. If the HTTP header authorization field contains a valid JWT token and the user is in the “Admin” role, a list of all users is returned. One if there is no token, the token is illegal, or the role does not match401 Unauthorized
The response will be returned./users/:id
– Only authenticated users with any role can access secure routes and accept HTTP GET requests; If the authorization succeeds, the user record is returned based on the specified “ID” parameter. Note that “Admin” can access all User records, while other roles (such as “User”) can only access their own records.
The projects in the tutorial can be found on GitHub: github.com/cornflourbl…
Run the role-based authorization API in Node.js locally
- Download or clone lab project from the above URL
- run
npm install
Install required dependencies - run
npm start
Launch the API and see successServer listening on port 4000
Run the vue. js client application
In addition to testing the API directly with applications such as Postman, you can also run a written Vue project to view it:
- Download vue.js project code: github.com/cornflourbl…
- run
npm install
Install required dependencies - To access the data returned by our Node.js instead of using the Vue project’s native fake data, remove or comment it out
/src/index.js
The file containsconfigureFakeBackend
The two lines - run
npm start
Start the application
Node.js project structure
- _helpers
- authorize.js
- error-handler.js
- role.js
- users
- user.service.js
- users.controller.js
- config.json
- server.js
The project consists of two main subdirectories. One is feature directory (Users) and the other is _helpers.
The example currently contains only one Users feature, but adding other features can be organized in the same pattern.
Helpers directory
Path: / _helpers
Contains code that can be used for multiple features and other parts of the application, and is named with an underscore prefix to group them prominently.
Role middleware
Path: / _helpers/the authorize. Js
const expressJwt = require('express-jwt');
const { secret } = require('config.json');
module.exports = authorize;
function authorize(roles = []) {
// The rule argument can be a simple string (e.g. Role.user or 'User')
// Can also be arrays (such as [role-.admin, role-.user] or ['Admin', 'User'])
if (typeof roles === 'string') {
roles = [roles];
}
return [
// Authenticate the JWT token and attach the user (req.user) to the request object
expressJwt({ secret }),
// Role-based authorization
(req, res, next) => {
if(roles.length && ! roles.includes(req.user.role)) {// Unauthorized user role
return res.status(401).json({ message: 'Unauthorized' });
}
// Both authentication and authorization are availablenext(); }]; }Copy the code
Authorized middleware can be added to any route to restrict access to authenticated users of a certain role. If the role parameter is left blank, the route is applied to any authenticated user. The middleware is then applied in the users/users. Controller. Js.
Authorize () actually returns two middleware functions.
The first of these (expressJwt({secret})) implements authentication by verifying Authorization in the HTTP request header. Upon successful authentication, a User object is attached to the REQ object, which contains the data from the JWT token, in this case the user ID (req.user.sub) and the user role (req.user.role). Sub is the standard attribute name in JWT and represents the ID of the item in the token.
The second middleware function returned checks the scope of access granted to the authenticated user based on the user role.
If both authentication and authorization fail, a 401 Unauthorized response is returned.
Global error handling middleware
Path: / _helpers/error – handler. Js
module.exports = errorHandler;
function errorHandler(err, req, res, next) {
if (typeof (err) === 'string') {
// Custom application error
return res.status(400).json({ message: err });
}
if (err.name === 'UnauthorizedError') {
// JWT authentication error
return res.status(401).json({ message: 'Invalid Token' });
}
// The default processing is 500 server errors
return res.status(500).json({ message: err.message });
}
Copy the code
The global error handling logic is used to catch all errors, and also to avoid miscellaneous processing logic throughout the application. It is configured as middleware in the main file server.js.
Role object/enumeration value
Path: / _helpers/role. Js
module.exports = {
Admin: 'Admin'.User: 'User'
}
Copy the code
The role object defines all the roles in the routine and is used like enumerated values to avoid passing strings; So you can use role-.admin instead of ‘Admin’.
User directory
Path: / users
The Users directory contains all the code specific to role-based authorization user features.
Customer service
Path: / users/user. Service. Js
const config = require('config.json');
const jwt = require('jsonwebtoken');
const Role = require('_helpers/role');
// This simply hardcodes user information, which should be stored in the database in production
const users = [
{ id: 1.username: 'admin'.password: 'admin'.firstName: 'Admin'.lastName: 'User'.role: Role.Admin },
{ id: 2.username: 'user'.password: 'user'.firstName: 'Normal'.lastName: 'User'.role: Role.User }
];
module.exports = {
authenticate,
getAll,
getById
};
async function authenticate({ username, password }) {
const user = users.find(u= > u.username === username && u.password === password);
if (user) {
const token = jwt.sign({ sub: user.id, role: user.role }, config.secret);
const{ password, ... userWithoutPassword } = user;return{... userWithoutPassword, token }; }}async function getAll() {
return users.map(u= > {
const{ password, ... userWithoutPassword } = u;return userWithoutPassword;
});
}
async function getById(id) {
const user = users.find(u= > u.id === parseInt(id));
if(! user)return;
const{ password, ... userWithoutPassword } = user;return userWithoutPassword;
}
Copy the code
The user service module contains a method to authenticate user credentials and return a JWT token, a method to get all users in the application, and a method to get individual users by ID.
Because of the focus on authentication and role-based authorization, user arrays are hardcoded in this example, but in a production environment it is recommended to store user records in a database and encrypt passwords.
User controller
Path: / users/users. Controller. Js
const express = require('express');
const router = express.Router();
const userService = require('./user.service');
const authorize = require('_helpers/authorize')
const Role = require('_helpers/role');
/ / routing
router.post('/authenticate', authenticate); // Expose the route
router.get('/', authorize(Role.Admin), getAll); // admin only
router.get('/:id', authorize(), getById); // All authenticated users
module.exports = router;
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user= > user
? res.json(user)
: res.status(400)
.json({ message: 'Username or password is incorrect' }))
.catch(err= > next(err));
}
function getAll(req, res, next) {
userService.getAll()
.then(users= > res.json(users))
.catch(err= > next(err));
}
function getById(req, res, next) {
const currentUser = req.user;
const id = parseInt(req.params.id);
// Only admins is allowed to access records of other users
if(id ! == currentUser.sub && currentUser.role ! == Role.Admin) {return res.status(401).json({ message: 'Unauthorized' });
}
userService.getById(req.params.id)
.then(user= > user ? res.json(user) : res.sendStatus(404))
.catch(err= > next(err));
}
Copy the code
The user controller module defines routes for all users. Routes using authorized middleware are restricted to authenticated users. Routes are restricted to specific administrator users if they include roles (e.g. authorize(role.admin)); otherwise (e.g. authorize()) routes apply to all authenticated users. Routes that do not use middleware are publicly accessible.
The getById() method contains some additional custom authorization logic that allows administrative users to access other users’ records, but prohibits ordinary users from doing so.
Application configuration
Path: / config. Json
{
"secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
}
Copy the code
Important: The “secret” attribute is used by the API to sign and validate JWT tokens for authentication, and should be updated to your own random string to ensure that no one can generate a JWT to gain unauthorized access to your application.
Master server entry
Path: / server. Js
require('rootpath') ();const express = require('express');
const app = express();
const cors = require('cors');
const bodyParser = require('body-parser');
const errorHandler = require('_helpers/error-handler');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cors());
/ / API routing
app.use('/users'.require('./users/users.controller'));
// Global error handling
app.use(errorHandler);
// Start the server
const port = process.env.NODE_ENV === 'production' ? 80 : 4000;
const server = app.listen(port, function () {
console.log('Server listening on port ' + port);
});
Copy the code
Server.js, as the main entry point of the API, configures the application middleware, binds the routing control, and starts the Express server.
–End–
Search fewelife concern public number reprint please indicate the source