Let's create a /api/login route to authenticate a user using a username and password:
- First, we will need to create a new /routes/api/login.js route configuration to manage our API authentication. This route configuration will be similar to our /routes/api/posts.js route configuration that contains both a users store of user records and a JSON API serializer configuration:
var express = require('express');
var router = express.Router();
var JSONAPISerializer = require('jsonapi-serializer').Serializer;
var users = [{
id: "1",
firstName: "Nicholas",
lastName: "McClay",
email: "[email protected]",
password: 'Secret',
role: 'admin'
}];
var serializer = new JSONAPISerializer('users', {
attributes: ['firstName', 'lastName', 'email'],
});
module.exports = router;
- We will create a login middleware that will check a username and password provided in a request body against the available user records in our users store. We will also create a POST route to pass in our middleware and write an authenticated user to our Express request session:
...
var login = function(req, res, next) {
var username = req.body.username;
var password = req.body.password;
if (username && password) {
var match = users.find(function(user) {
return user.email === username && user.password === password;
});
if (match) {
req.session.user = match;
next();
}
}
};
router.post('/', login, function(req, res, next) {
req.session.save(function (err) {
var user = req.session.user;
res.json(serializer.serialize(user));
});
});
...
- We will also need to add error handling to our login middleware. We will return JSON API error objects if we don't receive the correct parameters or receive invalid credentials:
var express = require('express');
var router = express.Router();
var JSONAPISerializer = require('jsonapi-serializer').Serializer;
var JSONAPIError = require('jsonapi-serializer').Error;
...
var authError = function(message) {
return new JSONAPIError({
status: 401,
title: 'Invalid Authorization Parameters',
detail: message
});
};
var login = function(req, res, next) {
var username = req.body.username;
var password = req.body.password;
if (username && password) {
var match = users.find(function(user) {
return user.email === username && user.password === password;
});
if (match) {
req.session.user = match;
next();
} else {
res.status(401).json(authError('Invalid username or password for user authentication.'));
}
} else {
res.status(401).json(authError('Must provide username or password for user authentication.'));
}
};
...
- Next, we will need to update our /middleware/auth.js middleware's requireRole method to use our new session.user property. We will inspect the user session for a valid role property and either allow or reject the request accordingly:
...
var JSONAPIError = require('jsonapi-serializer').Error;
module.exports = {
..
requireRole: function (role) {
return function (req, res, next) {
var user = req.session.user;
if (user && user.role && user.role === role) {
next();
} else {
res.status(403).json(new JSONAPIError({
status: 403,
title: 'Requires ' + role + ' Role',
detail: 'You do not have the correct authorization to access this resource.'
}));
}
}
}
};
- Finally, let's include our new login route configuration into our /routes/api.js route configuration. To test it, we can also experiment by requiring a user to be authorized as an admin to visit our /posts route:
...
var auth = require('../middleware/auth');
var login = require('./api/login');
...
router.get('/', function (req, res, next) {
res.send('API is running');
});
router.use('/login', login);
router.use('/posts', auth.requireRole('admin'), posts);
...
- Now, when we attempt to visit /api/posts with our API client, we will get an 403 unauthorized error. After sending a POST request with the body:
{"username": "[email protected]", "password": "Secret"}
to our /api/login API end-point, we will get back our serialized user record. After this we will be able to successfully access /api/posts until we restart our web server, which will invalidate all our sessions again.