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 UnauthorizedThe 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

  1. Download or clone lab project from the above URL
  2. runnpm installInstall required dependencies
  3. runnpm startLaunch 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:

  1. Download vue.js project code: github.com/cornflourbl…
  2. runnpm installInstall required dependencies
  3. 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.jsThe file containsconfigureFakeBackendThe two lines
  4. runnpm startStart 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