CHAPTER-5

A Complete Server: Authentication

Without authentication we can’t know who is using our service, and without authorization, we can’t control their actions. Our API currently has neither, and this is a problem; we don’t want any random user to be able to create and delete our products.

When our API is running on our laptop, we don’t need to worry about authentication. We’re the only people who can make requests to the API. However, once our API is hosted on a server and is publicly accessible on the internet, we’ll want to know who is making the requests. In other words, we’ll want our requests to be authenticated. This is important, because without authentication, we won’t be able to selectively limit access.

Once we can identify who a request is coming from, we can then make decisions on whether or not we allow access. For example, if we are making the request, we should have full access (it’s our API, after all). Conversely, if it’s a stranger, they should able to view products, but they should not be authorized to delete anything.

In this chapter we’re going to incrementally build up our APl’s security model. We’re going to start with a solid foundation where we can authenticate an admin user, and set up authorization for a set of private endpoints. By the end of the chapter we’ll have a full database-backed user model where authenticated users will be authorized to create and view their own orders.

Private Endpoints

Currently our API has no restrictions on what a user can do. If our API were to be deployed in production, it could be a disaster. Anybody could create an delete products at any time. This is a problem, and so the first thing we need to do is to start restricting access to private endpoints.

The first step towards locking things down is to identify requests coming from an admin user. There are many ways to do this, but we’ll start with a very straightforward and common approach: password and cookie.

To start, we come up with a secret password that only the admin user will know. Then we create an endpoint where the admin can log in. The admin user proves their identity by sending their secret password to the log in endpoint. If the password is correct, the API responds with a cookie. When the server receives any subsequent requests with a valid cookie, it knows the user is the admin and can allow access to private endpoints.

Here’s a diagram of the admin logging in, getting the cookie, and using it to create a product:

Our admin user authenticating with our API

Using this approach, we can limit access to all of our private endpoints (everything except the ones to view products). Modifying products and orders (create, edit, delete) should only be done by admins. Orders are also private for now; later in this chapter we’ll allow customers to create accounts, and we can allow an authenticated customer to create and view their own orders.

Authentication With passport

To add authentication to our app we’ll use the popular module passport. Passport is authentication middleware for Node.js. It’s flexible, modular, and works well with express. One of the best things about it is that passport supports many different authentication strategies including username and password, Facebook, Twitter, and more.

The passport module provides the overall framework for authentication, and by using additional strategy modules, we can change how it behaves. We’re going to start with username and password authentication, so we’ll want to also install the passport – loca I strategy module.

Once we get everything hooked up, our app will have an endpoint /log i n that accepts a POST body object with username and password properties. If the username and password combination is correct (we’ll get to how we determine this in a second), our app uses the express -sess ion module to create a session for the user. To create a session, our app generates a unique session ID (random bytes like 7H29Oz86PTUhT 1DyuTEMa5TNdZCyDcwM), and it uses that ID as a key to store a value that represents what we know about the user (e.g. {username:  ‘ admin ‘ } ). This session ID is then sent to the client as a cookie signed with a secret (if a client alters a cookie the signature won’t match and it will be rejected). When the client makes subsequent requests, that cookie is sent back to the server, the server reads the session ID from the cookie (we’ll install cookie – parses for this), and the server is able to retrieve the user’s session object ({username : ‘ admin ‘ } ). This session object allows our routes to know if each request is authenticated, and if so, who the user is.

By making a few changes to server . js to use passport and passport -local we can start to protect our routes. The first thing we need to do is to require these modules:

const passport - require('passport')
const Strategy - require('passport-local' ).Strategy 
const cookieParser - require('cookie-parser') 
const expresssession - require('express-session')

We then choose which secret we’ll use to sign our session cookies and what our admin password will be:

const sessionsecret - process.env.SESSION_SECRET || 'mark it zero' 
const adminPassword = process.env.ADMIN_PASSWORD || 'iamthewalrus'

Simple secrets and passwords are fine for development, but be sure to use more secure ones in production. It's worth reading up on the the dangers of a simplistic session secret" and related prevention techniques.

Next, we configure passport to use our passport – local strategy:

passport.use(
  new Strategy(function (username, password, cb) (
    const isAdmin - (username --- 'admin' ) && (password === adminPassword) 
    if (isAdmin) cb(null,	username: 'admin' })

    cb(null, false)

We can think of a passport strategy like passport- local as middleware for passport. Similar to how we can use many different middleware functions to operate on our request and response objects as they come into express, we can use many different passport strategies to modify how we authenticate users. Depending on the strategy, configuration will be a little different. Ultimately, the purpose of a strategy is to take some input (e.g. a username and password) and if valid, return a user object.

How passport uses strategies

In the case of passport – local, we need to provide a function that accepts username, passport, and a callback. That function checks the provided username and password, and if valid, calls the callback with a user object. If the username and password don’t match, the function calls the callback with false instead of the user object.

After passport- local does its thing, passport can handle the rest. passport is able to make sure that the user object is stored in the session and accessible to our middleware functions and route handlers. To do this, we need to provide passport methods for serializing the user object to and deserializing the user object from session storage. Since we’re keeping things simple, we’ll store the user object in our session as is, and therefore our serialize/deserialize methods will both be identity functions (they just return their arguments/inputs):

passport.serializeUser((user, cb) =› cb(null, user)) 
passport.deserializeUser((user, cb) =› cb(null, user))

We can think of session storage as a simple object with keys and values. The keys are session IDs and the values can be whatever we want. In this case, we want it to be the user object itself. If we kept a lot of user details in the database, we may just want to store the username string when we serialize, and do a database lookup to get the latest, full data when deserializing.

We’ve now configured passport to use to store user info in the session, but we need to configure express to use cookie- parses and express -session as middleware before it will work properly. Out of the box, express won’t automatically parse cookies, nor will it maintain a session store for us.

app. use(cookieParser()) 
app. use(
  expresssession({
    secret: sessionsecret,
    resave: false,
    saveUninitialized : : false
})

Note that when configuring expresssess ion, we provide sessionsecret so we can sign the cookies. We also set resave 70 and saveUnintialized’’ to false as both are recommended by express – session documentation.

Next up, we tell express to use passport as middleware by using its initialize(  ) and  session( ) methods:

app.use(passport.initialize()) 
app. use(passport.session())

We’ve now got passport all hooked up, and our app can take advantage of it. The two things we need to do are to handle logins and to protect routes. Here’s how we have passport handle the login:

app.post('/login', passport.authenticate('local'), (req, res) =› 
  res.json({ success: true })

Up until this point we’ve only been using global middleware. However, when defining route handlers, we can also use middleware specific to that route. Before now, we used the form:

app[method](route, routeHandler)

When setting up this route we do this instead:

app[method] (route, middleware, routeHandler)

It will behave exactly like before with one key difference; the middleware function will run before the route handler. Just like global middleware, this method has the opportunity to prevent the route handler from ever running. In this case, this passport . authenticate( ) method will act as a filter or gatekeeper. Only if authentication is successful will we respond with success.

By calling passport . authenticate( ‘ local ‘ ) we are using passport to create a login middleware function for us that uses the passport – local strategy that we configured above.

We can also use this route middleware pattern to protect other route handlers. If a user has used the login route successfully, they would have been given a signed cookie with their session ID. Any subsequent requests will send that cookie to the server allowing route handlers to see that user object. This means that we can create our own middleware that looks for that user object, before allowing a route handler to run. Here’s what our routes look like when they’re protected by an ensureAdmin middleware function:

app.post('/products', ensureAdmin, api.createProduct) 
app.put('/products/:id', ensureAdmin, api.editProduct) 
app.delete( '/products/: id', ensureAdmin, api.deleteProduct)

app.get('/orders', ensureAdmin, api.listorders)
 app.post('/orders', ensureAdmin, api.createOrder)

And here’s how we define ensureAdmin:

function ensureAdmin (req, res, next) {
  const isAdmin = req.user && req.user.username === 'admin' 
  if (isAdmin) return next()

  res.status(40{).json({ error: 'Unauthorized' })

req.user is available to us thanks to passport and express – session. If the user has previously authenticated, we’ll be able to access their user object here.

The route handler will only be able to run if we call next( ), and we’ll only do that if the request comes from an admin user. Otherwise, we respond with a 401, unauthorized error.

Now that we’ve created a /login route and authorization middleware we can see passport in action. Let’s check to see if our admin routes are protected. First we’ll make an unauthenticated request to our protected /orders route:

0 curl -i http://localhost:133T/orders

We can see that our ensureAdmin middleware is doing its job. The HTTP status code is 48a Unauthorized. We can no longer get a list of orders if we are unauthenticated:

1   HTTP/I . I 401 Unauthorized
2   X- Powered - By: Express
3   Access -Control -Allow-origin :  *
4   Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS, XMODIFY
5   Access-Control-Allow-Credentials: true
6   Access-Control-Max-Age: 86400
7   Access-Control-Allow-Headers: X-Requested-With, Z-HTTP-Method-Override, Content-Type\
8     Accept
9   Content-Type: application/json; charset=utf-8
10  Content - Length : 24
11  ETag : V/ " t8-XPDV80vbhk4yYt /PADG4jYh4rSI "
12  Date : Tue, 04 Jun 20t9 t5 : t8 : 00 GNT t3	 Connection : keep - alive
14
15  ( "error" : "Unauthorized" )

Now we’ll use the following curl command to log into our app and store any received cookies in the file, cookies:

curl - i X POST \
  - H ' content - type : application /json '  \
  -d "("username": "admin", "password": "iamthewalrus"}" \
  --cookie-jar cookies \ 
  http://localhost:i337/login

With the correct password, we’ll see a successful response:

1   HTTP/I . 1 200 OK
2   X-Powered-By: Express
3   Access-Control-Allow-Origin: *
4   Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS, XHODIFY 
5   Access-Control-Allow-Credentials: true
6   Access-Control-Nax-Age: 86400
7   Access-Control-Allow-Headers: X-Requested-With, X-HTTP-Nethod-Override,
8    Accept
9   Content-Type: application/json; charset=utf-8
10  Content-Length: 16
11  ETag: W/"10-oV4hJxRVSENxc/wX8+mA4/Pe4tA"
12  Set-Cookie: connect.sid=s%3Au0EcHKlSlbuMlwCkwpYSNoLtENePPC3C.Ex5ZDu8g9EgPzt3Ik8H7T3c\
13  L%2FRApDtUzyC8sKPCLJpQ; Path=/; Http0nly
14  Date: Tue, 04 Jun 2019 15:2T:12 GMT
15  Connection: keep-alive
16
17  {”success”:true}

The important thing is that we receive a cookie with our new session ID. By using the – -cookie – jar flag we should also have stored it in a local file for use later. Take a look at the newly created cookies file in the current directory:

  1. 0 cat cookies
  2. # Netscape HTTP Cookie File
  3. # https://curl.haxx.se/docs/http-cookies.html
  4. # This file was generated by libcurl ! Edit at your own risk.
  5. "Http0nly_localhost FALSE  /      FALSE 0      connect.sid  s%3AHe-NtUCB\
  6. p50dQYEyF8tnSzF22bMxo15d.xapHXgFEH0dJcSp8ItxWn6F89w2NWj%2BcJsZ8YzhCgFM

Here we can see that we did receive a cookie. The key is connect . sid and the value is s%3AHe – NtU . This value contains our session ID, so now we can make another request and the server will be able to look up our session.

The cookie value is url encoded so characters become %3A. This value that we is prefixed with s: to show that it's a session ID. The remainder of the value is the session ID and the signature joined with a . The format of the value is s: $ {session Id} . $(signature) , and in this case the session ID is He - NtUCBp58dQYEyF8tnSzF22bNxol5d.

Now we’ll make an authenticated request using our new cookie file:

0 curl -i --cookie cookies http://localhost:i33T/orders

This time we get a 200 OF status. If we had any orders to list, we would see them instead of an empty array. Everything is working now:

1  HTTP/1.1 200 OK
2  X-Powered-By : Express
3  Access-Control-Allow-Origin: *
4  Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS, XMODIFY
5  Access-Control-Allow-Credentials: true 
6  Access-Control-Max-Age: 86400
7  Access-Control-Allow-Headers: X-Requested-With, X-HTTP-Nethod-Override,
8   Accept
9  Content-Type: application/json; charset-utf-8
10 Content - Length :  2
11 ETag : W/ " 2 - T9Fw4VU0Tkr8CvBTL4zaMCqX Z8w"
12 Date : I'ue , 04 Jun 20t9 t5 : 22 : 5t  GIFT
13 Connection: keep-alive
14
15 [)

We’ve added authentication and authorization piece-by-piece, but it would be good to now take a look at the whole file to see how everything fits together:

const express - require('express') 
const passport = require('passport')
const Strategy - require('passport-local' ).Strategy 
const bodyParser = require('body-parser' )
const cookieParser - require('cookie-parser') 
const expresssession = require('express-session')

const api = require('./api')
const middleware - require(' . /middleware' )

const port - process.env.PORT || 4337
const sessionsecret = process.env.SESSION_SECRET || 'mark it zero' 
const adminPassword - process.env.ADMIN_PASSWORD l l ' iamthewalrus'

passport.use(
  new Strategy(function (username, password, cb) {
    const isAdmin - (username --- 'admin' ) && (password ==- adminPassword) 
    if (isAdmin) cb(null, ( username: 'admin' })

   cb(null, false)



passport.serializeUser((user, cb) =› cb(null, user)) 
passport.deserializeUser((user, cb) =› cb(null, user))

const app - express()

app.use(middleware.cors) 
app.use(bodyParser.json()) 
app.use(cookieParser()) app.use(
  express session((
    secret:  sess ionSecret ,
 


    resave: false,
    saveUninitialized: false



app. use(passport. initialize()) 
app. use(passport.session())

app.post( '/login', passport.authenticate(' local '), (req, res) =› 
  res.json({ success: true })

app.get('/products', api.listProducts) 
app.get('/products/:id', api.getProduct) 
app.post('/products', ensureAdmin, api.createProduct) 
app.put('/products/:id', ensureAdmin, api.editProduct) 
app.delete('/products/:id', ensureAdmin, api.deleteProduct)

app.get('/orders', ensureAdmin, api. listorders) 
app.post( '/orders', ensureAdmin, api.createOrder)

app.use(middleware.handleValidationError) 
app. use(middleware.handleError) app.use(middleware.notFound)

const server = app.listen(port, () =› 
  console.log(’Server listening on port ${port}’)


function ensureAdmin (req, res, next) {
  const isAdmin - req.user && req.user.username --- 'admin' 
  if (isAdmin) return next()

  res.status(401).json({ error: ' Unauthorized' })

Creating an Auth Module

When building an app it’s important to keep our codebase clean and maintainable. After this last section, we’ve added a bunch of new authentication logic to our server . js file. Our app will continue to work just fine if we leave that code where it is, but it does make it harder to see what routes are active at a glance with it in there.

To clean things up we’re going to create a new module auth . js to house our authentication and authorization related logic. This will also be useful as we iterate further and add more auth-related features.

After pulling out all the auth-related logic, here’s our server . js module:

const express - require('express')
const bodyParser = require(' body-parser ' ) 
const cookieParser - require('cookie-parser')

const api - require('./api') 
const auth = require(' ./auth')
const middleware - require(' . /middleware' ) 

const port - process.env.PORT || 1337 

const app = express ( )

app.use(middleware.cors) 
app.use(bodyParser.json()) 
app.use(cookieParser()) 
auth.setMiddleware(app)

app.post('/login', auth.authenticate, auth.login)

app.get('/products', api.listProducts) 
app.get('/products/:id', api.getProduct) 
app.post('/products', auth.ensureAdmin, api.createProduct) 
app.put('/products/:id', auth.ensureAdmin, api.editProduct)
app.delete('/products/:id', auth.ensureAdmin, api.deleteProduct)

app.get('/orders', auth.ensureAdmin, api.listOrders) 
app.post( '/orders', auth.ensureAdmin, api.createOrder)

app. use(middleware.handleValidationError) 
app. use(middleware.handleError) \
app.use(middleware.notFound)

const server app.listen(port,  ()  '›
  console . log (’ Server listening on port $(port)’ )

This file still has the same structure as it did in the last section. The only difference is that all auth- related logic lives in auth . js now. This makes it easier to quickly see all of our middleware and routes.

Our auth . j s module does need to set up some middleware of its own: expresssession( ) , passport . intialize( ) , passport . session( ) . To make this easy, we create a method auth . setMiddleware( ) that takes app as an argument.

auth.setMiddleware(app)

Then in auth . js, here’s how we use it:

function setMiddleware (app) ( 
  app.use(session()) 
  app.use(passport.initialize()) 
  app.use(passport.session())

session( ) is the same as before:

function session () ( 
  return expresssession((
    secret: sessionsecret, 
    resave : false , 
    saveUninitialized: false

After we have the middleware taken care of, we use auth . js  to create the /login route:

app.post('/login', auth.authenticate, auth.login)

auth . authenIicate is just a reference to passport . authenticate( ‘ local ‘ ) , and auth . login ( ) is as simple as it was before. If auth . authenticate( ) fails, it will never run:

function login (req, res, next) { 
  res.json({ success: true })

Finally, we use auth . ensureAdmin( ) to protect our admin routes:

app.post('/products', auth.ensureAdmin, api.createProduct) 
app.put('/products/:id', auth.ensureAdmin, api.editProduct) 
app.delete('/products/:id', auth.ensureAdmin, api.deleteProduct)

app.get('/orders', auth.ensureAdmin, api.listorders) 
app.post('/orders', auth.ensureAdmin, api.createOrder)

We’ve only made a sleight tweak to how this works:

function ensureAdmin (req, res, next) {
  const isAdmin - req.user && req.user.username --- 'admin' 
  if (isAdmin) return next()

  const err = new Error('Unauthorized') 
  err.statusCode - 401
  next(err)

We’re now going to rely on the built-in error handling of express by using next( ) to pass the error to middleware. By setting the statusCode property of the error, our error-handling middleware can send the appropriate response to the client:

function handleError (err, req, res, next) { 
  console.error(err)
  if (res.headersSent) return next(err)

  const statusCode = err.statusCode I I 500
  const errorMessage = STATUS_C0DES[statusCode] || 'Internal Error' 
  res.status(statusCode).json(( error: errorMessage })

By default we’ll send a 588 Internal Error response, but if the error has a statusCode property, we’ll use STATUS_C0DES to look up the HTTP error.

NOTE: The core http module has a STATUS_CODES object that is a collection of all the standard HTTP response status codes and descriptions. For example, require( ' http ' ) . STATUS_C0DES [484] === ' Not Found '

const passport = require('passport')
const Strategy - require('passport-local' ).Strategy 
const expresssession = require('express-session')

const sessionsecret = process.env.SESSION_SECRET || 'mark it zero' 
const adminPassword - process.env.ADMIN_PASSWORD l l ' iamthewalrus'

passport.use(adminstrategy()) 
passport.serializeUser((user, cb) =› cb(null, user)) 
passport.deserializeUser((user, cb) =› cb(null, user)) const authenticate	
passport.authenticate(' local ')

module . exports = ( 
  set middleware, 
  authenticate, 
  login, ensureAdmin


function setMiddleware (app) { 
  app.use(session()) 
  app.use(passport.initialize()) 
  app.use(passport.session())


function login (req, res, next) ( 
  res.json({ success: true })


function ensureAdmin (req, res, next) {
  const isAdmin - req.user && req.user.username =-- 'admin' 
  if (isAdmin) return next()

  const err - nen Error('Unauthorized' ) 
  err.statusCode - 401
  next( err)



function adminstrategy () {
  return new Strategy(function (username, password, cb) {
    const isAdmin - username --- 'admin' && password --- adminPassword 
    if (isAdmin) return cb(null, { username: 'admin' })

    cb(null, false)



function session () (
  return expresssession(( 
    secret: sessionsecret, 
    resave: false,
    saveUnintialized :false

Our app is much cleaner now. We’ve isolated all auth responsibility to a single module which is helpful. However, we haven’t solved an annoying shortcoming of our implementation related to sessions.

Session Sharing and Persistence with JWTs

In this chapter we’ve looked at how after an admin user successfully logs in, the server keeps track of that login via session storage. As of right now, that session storage object lives in memory. This causes two problems.

The first problem is that our server doesn’t persist this data to disk. Each time our server restarts, this data is lost. We can test this out by logging in, restarting our app, and then attempting to use a protected route. We’ll get an unauthorized error. This happens because after logging in, our client will receive a cookie. This cookie contains a session ID that points to a value in the session storage. After the server restarts, that sessions storage is cleared. When the client makes another request using the old session ID, there’s no longer any information associated with that key.

The second problem is that if we had two instances of the server running, session data would not be shared between them. This means that if our client logs into and receives a cookie from server instance A, that cookie would not work on server instance B. This is because server instance B would not have any information associated with server instance A’s cookie’s session ID.

We'll talk more about this when we get into production deploys, but having multiple instances is important when we want to scale and to prevent downtime during deployments and maintenance.

We’ve already discussed persistence when talking about other forms of data. In those cases we decided to use a database as this solves the problem of server restarts and accommodating multiple instances.

If we were to use a database for sessions, we could create a collection where the document ID is the session ID, and each time the server receives a cookie, it would look for the related document. This is a common approach, but adds database requests. We can do better.

To review, our app uses the session to keep track of who a particular user is. Once a user proves their identity by providing a username and password, we send back a generated session ID that is associated with their username. In the future, they’ll send back the session ID and we can look up the username in storage.

We can avoid using sessions entirely by using JSON Web Tokens (JWT). Instead of sending the user a session ID that is associated with username, we can send the user a token that contains their username. When we receive that token back from the user, no lookups are required. The token tells the server what their username is. This may sound dangerous; why can’t anybody just send us a token with the admin username? This isn’t possible because with JWT, tokens are signed with a secret; only servers with the secret are able to create valid tokens.

Another benefit of using JWTs is that we don’t need the client to use cookies. Our server can return the token in the response body. Afterwards the client only needs to provide that token in an authorization header for authentication. Cookies are convenient for browsers, but they can be complicated for non-browser clients.

Since we’ve moved all auth-related logic to auth . js, almost all of the changes will be limited to that module. Most of what we need to do is to replace express – session and related functionality with jsonwebtoken. To get started we change our requires and initial setup:

const jwt - require('jsonwebtoken') 
const passport = require('passport')
const Strategy - require('passport-local' ).Strategy 

const autoCatch - require('./lib/auto-catch')

const jwtsecret - process.env.UWT_SECRET || 'mark it zero'
const adminPassword = process.env.ADMIN_PASSWORD || ' iamthewalrus' 
const jwtopts - { algorithm: ' HS256' , expiresIn : '30d ' }

passport.use(adminstrategy())
const authenticate = passport.authenticate('local', { session: false })

We no longer require express -session, and instead we require jsonwebtoken. To configure jwt we choose which algorithm we’d like to use to sign tokens and how long it takes for them to expire. For now we choose HS2s6 and 30 day expiration.

We'll cover secrets in more depth in our chapter on deployments. However, now is a good time to point out that choosing strong secrets is important. As long as we make a point of using strong secrets for our production deploys, the secrets we use in development don't matter. Unfortunately, many people forget to do this and use weak development secrets in production. Our secret should be provided by process . env . O1#T_SECRET that only our production server would know. We set a simple secret as a fallback for convenience in development. Additionally, our production secret should be random and long enough to resist brute-force and dictionary-based cracking (at least 32 characters for HS256). For more information on this related to JWT read Brute Forcing HS256 is Possible: The Importance of Using Strong Keys in Signing JWTs’2.

We’ve also removed calls to passport . serializeUser( ) and  passport . deserializeUser( ) since we are no longer storing authentication information in the session object. In addition, when we configure our authenticate( ) function, we provide the option to disable using a session.

Since we’re not using the session, we don’t need to use setMiddleware( ) anymore. The express – session, passport . initialize( ) , and passport . session( ) middleware can all be removed. We’ll also be sure to remove the auth . setMiddleware( ) call from server . js. Our app is becoming much simpler.’

Next up, we change our Iogin( ) route handler to use jwt:

async function login (req, res, next) {
  const token - await sign({ username: req. user. username }) 
  res.cookie( 'jwt', token, { httponly: true })
  res.json({ success: true, token: token })

Just like before, this will only run if passport – local finds the user. If it does, this route handler will get called and information related to the authenticated user will be available on the req . user object.

First, we take the user’s username and use jet to create a signed token. We’ll take a look at how we do this with sign( ) in a second. Then we send the token in the response in two places: a cookie (under the key jwt) and in the response body itself. By providing it in two places we make it easy for the client to decide how it wants to use it. If the client is a browser, the cookie can be handled automatically, and if the client prefers to use authorization headers it’s nice to provide it to them in in the body.

Our sign( ) method is a short convenience wrapper around jwt . sign( ) that automatically uses our jwtsecret and jwtopts:

async function sign (payload) {
  const token - await jwt.sign(payload, jwtsecret, jwt0pts) 
  return token

After this, we change our ensureAdmin( ) route middleware to be able to handle tokens:

async function ensureAdmin (req, res, next) {
  const jwtstring - req.headers.authorization || req.cookies.jwt
  const payload - await verify(jwtString)
  if (payload.username --- 'admin') return next()

  const err - nen Error('Unauthorized' ) 
  err.statusCode - 401
  next( err)

We give clients the option of providing the token via cookies or authorization header, so we check for it in both places. Afterwards we use jwt to verify it and get the contents of the token payload. Like before, if the user is the admin, we let them continue. If not, we give them an error.

Similar to sign( ), verify( ) is just a convenience wrapper. When clients send a token via authorization header, the standard is to have the token prefixed with ‘ Bearer  , so we filter that out if it’s present. Also, we want to make sure that if there’s an error, we set the statusCode property to 48a so that our handleError( ) middleware will send the client the proper error message:

async function verify (jwtstring — ' ') { 
  jwtstring - jwtString.replace(/’Bearer /i, ' ')

  try (
    const payload - await jwt.verify(jwtstring, jwtsecret)
    return  payload
  } catch (err) { 
    err.statusCode - 401 
    throw err

We’ve already seen how to use curl to log in and make authenticated requests with cookies. That still works, but now we can also authenticate using the authorization HTTP header to send our JWT. Let’s try that with curl.

First, we log in and store the JWT in a text file, admin . jwt. We can do this easily using jq to isolate the token key of the JSON response (the -r flag means “raw” and will omit quotes in the output):

0 curl —X POST \
       - H  ' content - type: application/json ’  \
       - d ’ ( "username"  "admi n " , "password "  "iamthewalrus" ) ' \
       http: // local host: 1337/Iogin \ 
  j q -r . token \
› admin . jwt

0 cat admin.jwt 
eyJhbOciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyU1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNTYwMTc0NzE\ 0LCJleHAiOjE1NjI3NjY3MTR9.C0zF543FRTzKBsOFHQ7ilDi_a9RqsMbq6VYMwJTFJM4

Now to make an authenticated request, we just put that token in an HTTP header. This is easy using command substitution”:

0 curl \
  -H ”authorization: Bearer $(cat admin.jwt)" \ 
  http://localhost:1337/orders

NOTE: The typical way to send a JWT to a server is as a bearer token. All this means is that we send it as an HTTP authorization header, and we prefix the token with Bearer . Bearer tokens are an IETF (Internet Engineering Task Force) standard7‘.

And that’s all we needed to do to convert our app from using sessions to JWT. Here’s the full updated auth . js module:

const jwt - require('jsonwebtoken') 
const passport - require('passport')
const Strategy - require('passport-local' ).Strategy

const autoCatch - require(' /lib/auto-catch')

const jwtsecret - process.env.dWT_SECRET || 'mark it zero'
const adminPassword - process.env.ADMIN_PASSWORD || ' iamthewalrus' 
const jwtopts - { algorithm: ' HS256' , expiresIn : '30d ' }

passport.use(adminstrategy())
const authenticate = passport.authenticate('local', { session: false })

module.exports = { 
  authenticate,
  login: autoCatch( login), 
  ensureAdmin : autoCatch(ensureAdmin)

async function login (req, res, next) {
  const token - await sign({ username: req. user. username }) 
  res.cookie('jwt', token, { httponly: true })
  res.json({ success : true, token: token })

async function ensureAdmin (req, res, next) {
  const jwtstring - req.headers.authorization || req.cookies.jwt 
  const payload = await verify(jwtString)
  if (payload. username --= 'admin') return next()
  
  const err - new Error('Unauthorized') 
  err.statusCode = 401
  next(err)

async function sign (payload) {
  const token - await jwt.sign(payload, jwtsecret, jwtopts) 
  return token
  

async :Function verify(jwtstring = ’ ' ) (
  jwtstring = jwtstring . replace( / Bearer / i ,  ’ ’ )

  try (
    const payload - await jwt.verify(jwtstring, jwtsecret) 
    return payload
  } catch (err) { 
    err.statusCode - 401 
    throw err

function adminstrategy () {
  return new Strategy(function (username, password, cb) (
    const isAdmin - username --- 'admin' && password --- adminPassword 
    if (isAdmin) return cb(null, ( username: 'admin ' })

    cb(null, false)
})

Adding Users

Our API’s endpoints are now protected. Only the admin user can modify products and see orders. This would be acceptable for an internal app. However, if we want to make our store public, we’d want regular users to be able to create orders and see their order history. For this we’ll need to introduce the concept of users to our app and develop a more nuanced authorization scheme.

For our protected routes, we have authorization that is all-or-nothing. If the user is the admin, they have special privileges; otherwise, they don’t. We’ll want to have routes that have more fine-grained levels of access that depends on how the user is authenticated:

If the user is…

  • Unauthenticated: no access
  • A Regular User: access limited to their own records
  • An Admin: full access

Before we can add this logic, our app first needs to be able to create, manage, and authenticate regular users. Let’s add an endpoint to create users.

First we add a new public route to our server . js module. We want anybody to be able to create an account with us:

app.post('/users', api.createUser)

And a corresponding method in api . js:

async function createUser (req, res, next) { 
  const user - await Users.create(req.body) 
  const ( username, email } = user 
  res.json({ username, email })

Notice that we limit the fields we’re sending back to the client. We don’t want to send back the hashed password.

Next, we create a user model. Our user model will be very similar to the models we’ve created in the chapter on persistence. We’ll use mongoose in almost the same way. The biggest difference is that we’re now dealing with passwords, and we have to be more careful.

NOTE: User credentials are a high value value target and it's important to store them with care. If you'd like to learn more, check out the OWASP (Open Web Application Security Project) Password Storage Cheat Sheet7’ and the OWASP Threat Model for Secure Password Storage“.

The naive approach would be to store a user document that looks something like:

username: 'donny',
email: 'donald%kerabatsos.io', 
password: 'I love surfing'

To log in as donny a user would send a POST with the username and password, and our API can check to see if those match the document stored in the database. If the user provides the correct password, we know who they are.

Unfortunately, storing users’ passwords like this is a big problem. If anyone were to gain access to our database they would have emails and passwords for all of our users. By using each email/password combination on other popular services it’s likely that bad actor would be able to compromise many accounts.

If we don’t want to store passwords, how can we authenticate a users? The answer is to perform a one-way transformation, on the user’s password and store that hash instead. Later when a user logs in, we can perform the same transformation and see if the result matches what we have in the database. Because the transformation is one-way, a bad actor would not be able to reconstruct the user’s original password and use it maliciously.

To perform these transformations and comparisons we’ll use bcrypt’7. Here’s a quick example of how to use it:

const bcrypt - require('bcrypt')
const SALT_ROUNDS - 10

const user - { 
  username: 'donny',
  email: 'donald0kerabatsos. io' , 
  password' 'I love surfing'


user.password	await bcrypt.hash(user.password, SALT_ROUNDS)

console. log(user)
// {
// username: 'donny'
// email: 'donald@kerabatsos.io',
// password: '$2b$10$buuJliif7qFs104UNHdUY.E.9VTbFkTV8mdce08cNxlljtelLml125e'



console. log(await bcrypt.compare( 'randomguess' , user.password))
// false

console. log(await bcrypt.compare( ' I love surfing' , user.password))
// true

We use the bcrypt . hash( ) method to convert the plain-text password I  love  surfing to the hash $2b$18$buuOJliif7qFs1O4UNHdUY.E.9VT bFkTV8mdce88cNxIIjteLm125e. Then we can use the bcrypt . compare( ) method to check passwords against it to see if they match. If a user tried to log in with the password randomguess, the comparison would fail, but I  Iove surfing would succeed.

Now that we know how to safely store passwords, we can create our users . js  module. To start, we create a mongoose model:

const cuid - require('cuid') const bcrypt - require('bcrypt')
const ( isEmail, isAlphanumeric } - require('val idator') 

const db - require(' /db')

const SALT_ROUNDS	10

const User - db.model('User', (
  _id:	type: String, default: cuid }, 
  username: usernameschema(),
  password : { type: String, maxLength: 120, required : true }, 
  email: emailschema(( required: true })

This is just like the modules we’ve created previously, except now we require bcrypt. Here’s usernameschema ( ) :

function usernameschema () ( 
  return (
    type: String, 
    required: true, 
    unique: true, 
    lowercase: true, 
    minLength: 3,
    maxLength: 16, 
    validate: [

        validator: isAlphanumeric,
        message: props =› '${props.value} contains special

        validator : str => !str.match(/’admin$/i), 
        message: props => ' Invalid username'

        validator: function (username)	return isUnique(this, username) }, 
        message: props => 'Username is taken'

Compared to other fields we’ve set up, we have some new restrictions on username. In particular, we are enforcing uniqueness, length limits, and which characters can be used. Since admin is our superuser, we do not allow anybody to reserve that name. By setting lowercase: true, all usernames will be set to lowercase before saving. This is useful when combined with uniqueness; it means that we can’t have both donny and Donny as it would be confusing if they were different people.

By prohibiting special characters. We can easily prevent users from choosing an all whitespace username or something unreadable like \_(n) _/ . However, if we want to support international characters, we could use a custom validation function to be more fine- grained with which special characters are allowed and prohibited.

One thing to note here is a well-known mongoose gotcha. Notice that we use both unique: true and a custom isUnique( ) validation function. The reason is because unique: true is not actually a validation flag like minLength or required. unique : true does enforce uniqueness, but this happens because it creates a unique index in MongoDB.

If we were to rely solely on unique : true and we attempt to save a duplicate username, it’s MongoDB not mongoose that will throw an error. While this would still prevent the document from being saved, the type and message of the error will be different from our other validation errors. To fix this, we create a custom validator so that mongoose throws a validation error before MongoDB has a chance to save the document and trigger an index error:

async function isUnique (doc, username) {
  const existing - await get(username)
 return !existing || doc._id === existing._id

Our custom validator looks to see if a user document with that username already exists in the database. If there is no document, there’s no problem. If there is an existing document, we check to see if the document we’re trying to save has the same ID. This second step is important, we would never be able to edit a document without it. Obviously, any user we attempt to edit will already exist in the database, so we can’t just check for existence. We need to check to see if the existing document is the same as the one we’re trying to edit.

Now that we have our model and schema, let’s take a look at our get( ) , list( ), create( ) , edit( ) , and remove( ) methods. get( ) , list( ), and remove( ) will be very similar to what we’ve seen before:

async function get	( username) (
  const  user - await  User . findone( (  username  ) )
  return user

async function list (opts = {}) {
  const ( offset - 0, limit - 25 } - opts
  
  const users - await User. find()
    .sort({ _id: 1 })
    .skip(offset) limit(limit)

return users

async function remove (username) { 
  await User.delete0ne(( username })

create( ) and edit( ) are different, however; we need to account for password hashing using bcrypt:

async function create (fields) { 
  const user = new User(fields) 
  await hashPassword(user) 
  await user.save()
  return user

async function edit (username, change) { 
  const user - await get(username)
  Object.keys(change).forEach(key =› ( user[key] - change[key] }) 
  if (change.password) await hashPassword(user)
  await user.save()
  return user

async function hashPassword (user) {
  if (! user.password) throw user. invalidate( 'password', 'password is required' ) 
  if (user.password.length 12) throw user. invalidate( 'password' , ' password must at least 12 characters ' )
  user.password	await bcrypt.hash(user.password, SALT_ROUNDS)

create( ) is pretty straightforward. After we create a user instance, we need to hash the password so that we do not save it in plain-text. edit( ) is a little trickier: we only want to hash the password if it’s been changed. We do not want to hash a password twice.

In hashPassword( ), we perform some additional validation. We can’t do this on the model schema itself because these are rules that we want to enforce on the plain-text values — not the hashed value. For example, we want to make sure that the plain-text value is at least 12 characters. If we placed this validator on the schema, it would run after we hash the plain-text. We would check to see if the hashed password is greater than 12 characters, and this would be useless; the hash will always be longer.

Here’s the full users . js module:

const cuid = require('cuid')
const bcrypt - require('bcrypt')
const ( isEmail, isAlphanumeric } = require('validator')

const db = require(' ./db')

const SALT_ROUNDS = 10

const User = db.model('User', (
  _id: ( type: String, default: cuid }, 
  username: usernameschema(),
  password : { type: String, maxLength: i20, required : true }, 
  email: emailschema(( required: true })

module.exports - {

  list, 
  create , 
  edit , 
  remove ,
  mode 1 : User

async  :function get  ( username )  (
const user - await User.findone( { username })
return user

async function list (opts = {}) {
  const ( offset - 0, limit - 25 } - opts

  const users - await User.find()
    .sort({ _id: 1 })
    .skip(offset) 
    limit(limit)

  return users

async function remove (username) { 
  await User.deleteone({ username })

async function create (fields) { 
  const user - new User(fields) 
  await hashPassword(user) 
  await user.save()
  return user

async function edit (username, change) {
  const user = await get(username) 
  Object.keys(change).forEach(key -› ( user[key] - change[key] }) 
  if (change.password) await hashPassword(user)
  await user.save() 
  return user

async function isUnique (doc, username) { 
  const existing - await get(username)
  return !existing l l doc._id --- existing._id

function usernameschema () (
  return (
  type : String ,
  required : true,
  unique: true, 
  lowercase: true, 
  minLength : 3, 
  maxLength: i6, 
  validate : [

       validator: isAlphanumeric,
       message: props => '${props.value} contains special

       validator : sir	=+ ! sts . match( / admin$/ i ) , 
       message : props =› ' Invalid username '

       validator: function (username) ( return isUnique(this, username) }, 
       message: props =› 'Username is taken'

function emailschema (opts = {}) { 
  const ( required } - opts
  return (
    type: String, 
    required: ! !required, 
    validate' {
      validator: isEmail,
      message: props => ${props.value} is not a valid email address

async function hashPassword (user) {
  if (! user.password) throw user. invalidate('password', 'password is required' ) 
  if (user.password.length ‹ 12) throw user. invalidate( 'password' , ' password must
 at least 12 characters')

user.password	await bcrypt.hash(user.password, SALT_ROUNDS)

We can now use our API to create new users:

0 curl -iX POST \
        -H 'content-type: application/json' \
        — d ' (
          "username" :  "donny" ,
          "email"’ "donaldskerabatsos. io",
          ”password  ” :  ” i love surfing ” } '  \
        http: // localhost: i33T/ users

And the response we expect:

( "username" "donny", "email " "donald#kerabatsos . io" )

Of course if we try it again, we’ll get a validation error because that username is taken:

"error" : "User validation failed", 
"errorDetails”’ {
  "username”’ {
    "message" "Username is taken", 
    "name": "ValidatorError", 
    "properties"’ {
      "message"’ "Username is taken", 
      "type”’ "user defined",
      "path"’ "username",
      "value" ’ "donny"

   "kind "  "userdefined " ,
   "path": "username",
   "value"'"donny"

There’s one last step we need to take before we can log in with our new user account. If we don’t modify our passport – local strategy the auth . js module, we’ll get an unauthorized error:

D curl -iX POST \
        -H 'content-type: application/json' \
        -d '("username"’ "donny", "password"’ "I love surfing"} ' \ 
        http://localhost:t337/login
  1. HTTP/1.1 401 Unauthorized
  2. X-Powered-By: Express
  3. Access-Control-Allow-Origin: *
  4. Access-Control-Allow-Methods: POST, OET, PUT, DELETE, OPTIONS, XMODIFY
  5. Access-Control-Allow-Credentials: true
  6. Access-Control-Max-Age: 86400
  7. Access-Control-Allow-Headers: X-Requested-With, X-HTTP-Method-Override, Content-Type\
  8. Accept
  9. Date: Sun, 09 Jun 2019 {6: 42:32 GMT
  10. Connection: keep-alive
  11. Content-Length: 12
  12. Unauthorized

We need to change our strategy so that we use the users . js module instead of only checking if the request is correct for the admin:

function adminstrategy () {
  return new Strategy(async function (username, password, cb) {
    const isAdmin = username === 'admin' && password === adminPassword 
    if ( isAdmin) return cb(null, { username: 'admin' })

    try (
      const user = await Users.get(username) 
      if (!user) return cb(null, false)

      const isUser - await bcrypt.compare(password, user.password) 
      if (isUser) return cb(null, { username: user.username })
    } catch (err) { }

    cb(null, false)

Now we check two things. We make the same first check to see if the requesting user is the admin. Then, we check to see if we have a user in the database with the username from the request. If so, we use bcrypt to compare the plain-text password from the request to the hashed password stored in the database. If they match, we can authenticate the user.

With this change, we can now log in as our new user:

B curl —X POST \
        - H ' content - type: application/json ’ \
        - d ' ( "username"  "donny" ,  "password "	"I love surfing " } '  \
        http: // local host: t337/Iog i n



  "success" true,
  "token"’ "eyUhbGciOiUIUzI4NiIsInR5cCI6IkpXVCU9.eyU1c2VybmFtZSI6ImRvbm55IiwiaWF0Ijo\ xNTYwMDk5MTIzLCJleHAiOjE1NjI20TExMjN9.3JyvwbJqrkMDbXicWQxT9R_UaMKPZYQ3oLycHD2bd1Q"

User Authorization

New users can now sign up and log in, but they don’t have new capabilities. Our protected routes only work for the admin user. Any endpoint that works for a regular user would also work for unauthenticated requests.

Our app should have two tiers of authorization. One for our admin user who should have full access, and another for regular users who should only be able to access documents specifically related to their own account. For example, normal users should be able to create orders for themselves (but not for other users) and later view the orders they’ve created.

To make this work we’ll have to change our gatekeeper middleware, ensureAdmin( ) that we use to protect routes. Going forward, we don’t just want to know if the user is the admin, we also want to know if they are a normal user. Each endpoint can then use that information to decide how it should behave. For example, the endpoint to create a product will still require the user to be the admin, but the endpoint to list orders will simply restrict the query to only return records associated with the authenticated user.

This means that we’re going to shift more of the authorization responsibility to the route handlers themselves, rather than only using the router and middleware. In other words, we can user the router and middleware to specify which routes require authentication, but it’s the route handler itself that will determine how it behaves for an authenticated user.

This gives us a nice separation of responsibilities. Our gatekeeper middleware is only responsible for authentication, and our route handlers are responsible for authorization. It’s fine for our middleware to check to make sure that we know who the request is coming from, but once we know that, the route handlers are best equipped to determine who should be able to access what.

Let’s change our route handlers so that they expect a little more information about the authenticated user. We’ll start with api . create Product( ) , and change it from this:

async function createProduct (req, res, next) { 
  const product - await Products.create(req.body) 
  res.json(product)

to this:

async function createProduct (req, res, next) {
  if (!req.isAdmin) return forbidden(next)

  const product = await Products.create(req.body) res.json(product)


function forbidden (next) {
  const err - new Error('Forbidden') 
  err.statusCode - 403
  return next(err)

api . createProduct( ) is no longer relying on middleware to prevent access to non-admin users. However, it is relying on middleware to specify who the user is and whether or not they are an admin. Of course, we haven’t changed the middleware yet, so if we were to run this right now, it would block access to everyone, admin user included. In a minute, we’ll make sure that the req . isAdmin flag is properly set.

We've now changed our HTTP errors from 48a Unauthorized to 483 Forbidden. 481 Unauthorized is best used when we are preventing access to someone because we don't know who they are. In fact, many people think that it should be changed to “481 Unauthenticated” to better reflect how it should be used. 483 Forbidden on the other hand, should be used when we do know who the request is from, and they still don't have access to the requested resource.

We’ll also do the same tweak for our other admin-only route handlers:

async function editProduct (req, res, next) {
  if (!req. isAdmin) return forbidden(next)

  const change - req.body
  const product = await Products.edit(req.params.id, change) 
  
  res.json(product)

async function deleteProduct (req, res, next) { 
  if (!req. isAdmin) return forbidden(next)

  await Products.remove(req.params.id)
  res.json({ success: true })

We need to make different changes to api . createorder( ) and  api . listborders ( ) . These will no longer be admin-only, and not only that, they’ll behave slightly differently for non-admins.

api . createorder( ) will change from this:

async function createorder (req, res, next) { 
  const order = await Orders.create(req.body) 
  res.json(order)

to this:

async function createorder (req, res, next) { 
  const fields - req.body
  if (!req.isAdmin) fields.username - req.user.username

  const order - await Orders.create(fields)
  res.json(order)

We’ll make sure that api . createorder( ) can only be accessed by authenticated users. So unlike api . createProduct( ) and  api . deIeteProduct( ) we  don’t need to have any logic related to access prevention. However, we don’t want non-admins creating orders for other users. Users may only create an order for themselves. Therefore, if the user is not an admin, we make sure that the arguments to Orders . create( ) reflect this.

api . Iistorders( ) has a similar change. If the user is not the admin, we ensure that the model method’s response is scoped to their username:

async function listorders (req, res, next) {
  const { offset - 0, limit - 25, productId, status } - req.query

  const opts = (
    offset : Number ( offset ) ,
    limit : Number (limit) 
    productId, 
    status
 

if (!req. isAdmin) opts.username - req.user.username

const orders - await Orders.list(opts)

res.json(orders )

At this point, our route handlers would be effectively broken. We’ve modified them so that they all rely on req . isAdmin, but that flag is not set. Additionally, api . create0rder( ) and api . listborders( ) expect that normal users can access them, but both of those handlers are behind the ensureAdmin ( ) middleware.

Our final change will be to convert the ensureAdmin( ) middleware:

async function ensureAdmin (req, res, next) {
  const jwtstring - req.headers.authorization || req.cookies.jwt 
  const payload = await verify(jwtString)
  if (payload.username --= 'admin') return next()

  const err - new Error('Unauthorized') 
  err.statusCode = 401
  next(err)

to ensure User ( ):

async function ensureUser (req, res, next) {
  const jwtstring - req.headers.authorization || req.cookies.jwt 
  const payload = await verify(jwtString)

  if (payload.username) 
     req.user - payload
     if (req.user.username === 'admin') req.isAdmin = true
     return next()


 const err = new Error('Unauthorized') 
 err.statusCode - 401
 next(err)
 

Before, we’d stop any request that wasn’t from the admin user. Now, we allow any authenticated user through. In addition, we store information about the user in req . user, and if the user is the admin, we set req . isAdmin to true. If the request is not authenticated, we’ll stop it with a 401 Unauthorized.

Of course, now that we’ve changed the name of this method, we should be sure to change our export, and to use the new method in server . js:

app.post('/products', auth.ensureUser, api.createProduct) 
app.put('/products/:id', auth.ensureUser, api.editProduct) 
app.delete('/products/:id', auth.ensureUser, api.deleteProduct)

app.get('/orders', auth.ensureUser, api. listorders) 
app.post( '/orders', auth.ensureUser, api.createOrder)

Now any route that uses the ensureUser( ) middleware will be guaranteed to have an authenticated user. Additionally, if it’s the admin user, req . isAdmin will be set to true. This makes it very easy for each route handler to enforce their own authorization rules. Admin-only handlers can simply check req . isAdmin to determine access, and user-specific handlers can use seq . user to modify how they interact with model methods.

Our app has come a long way and has reached an important milestone: flexible permissions. Our app can now distinguish between anonymous, authenticated, and admin requests, and it can respond to each accordingly.

This pattern is good foundation for establishing more nuanced roles. We could imagine having users with a req . isNoderator flag that allows them to edit certain things like product reviews but not have full admin rights.

With authentication and authorization in place, our app is ready to be deployed so that real users can access it on the internet. In the next chapter we’ll go over all the things we need to consider when deploying an operating a production API.