Simple authentication example
Having persistent state across requests is a powerful tool, but sometimes it isn't immediately obvious what it can be used for. One possibility we will explore in this example is to create a secured service where consumers are required to login and have a cookie set before calling secured services. In this example we will secure the GET /users service in a prior example. The steps are as follows
- Create User - HTTP POST /users (Optional)
- Login User - HTTP POST /users/login
- Get Current User - HTTP GET /users
First we need a few helper functions, so we aren't duplicating code all over the place (DRY!). These will be used later by our actual routes:
const _ = require('lodash');
//helper functions
function findUser(username){
var user = _.find(usersList, { 'username': username });
return user;
}
function returnError(res, code, message){
return res.status(code).json({ error: { 'message': message } });
}
function guid() {
var s4 = function() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
//drop in authentication function for any route, will fail an invalid request, and return the user for a valid one
function authenticateSession(req, res){
var sessionId = (req.cookies) ? req.cookies.sessionId : undefined
if(sessionId == undefined || sessionId.length == 0 || sessionsList[sessionId] == undefined || sessionsList[sessionId].length == 0){
console.log("Failing Unauthenticated user")
returnError(res, 401, "Unauthenticated User")
return undefined
}
console.log("Authenticated user - " + sessionId)
return findUser(sessionsList[sessionId])
}
- Step 1 - Create a user (Optional)
The first optional step is to actually create a user that we are going to login as, this is a simple POST to /users. Any users we create using this route will be persisted in the Mock Server state until they expire or are reset. You could skip this step if you already have already created a user.
//user storage
state.users = state.users || []
var usersList = state.users
state.sessions = state.sessions || {}
var sessionsList = state.sessions
//create a user, body should be {username:"", password:"" ...}
mock.define('/users', 'post', function(req, res) {
if (req.body.isInvalid) {
return res.json(400, req.body);
}
var username = req.body.username;
if(findUser(username) !== undefined){
return returnError(res, 400, "User already exists");
}
usersList.push(req.body);
res.json({status: "ok"});
})
You can see in the above code we are creating two objects in our state map, an Array named 'users' and a Map named 'sessions'. When a new user is created they are added to users array, when we login in the next step, we will add something to the session map.
- Step 2 - Create a session
Now we have a user, we have some credentials to login with! In this example the /users/login route takes a username and password, and if valid will return the user details and a Set-Cookie header
//login as the given user, body should be {username:"", password:""}, response will be the user object and a cookie header
mock.define('/users/login', 'post', function(req, res) {
if (req.body.isInvalid) {
return res.json(400, req.body);
}
var username = req.body.username;
var user = findUser(username);
//if password matches, create session and cookie
if(user.password == req.body.password){
var sessionId = guid();
sessionsList[sessionId] = username;
res.header("Set-Cookie","sessionId="+sessionId);
return res.json(user)
}else{
return returnError(res, 401, "Invalid credentials");
}
})
- Step 3 - Get current user details
Using our new session cookie, we can now call the secured GET /users route that returns the details of the currently logged in user. This will return a user JSON payload if authenticated correctly, otherwise will return a 401 Unauthorised error if unauthenticated.
//get the current logged in user
mock.define('/users', 'GET', function(req, res) {
var user = authenticateSession(req, res);
if(user == undefined) return
return res.json(user);
})
This service is actually very simple, we are leveraging our helper function authenticatedSession() to do all the heavylifting of validating the cookie, and then just returning a user if it is found. You can imagine a much more complex set of services that all utilise this authentication mechanism to provide a very rich mocking experience.