API Design in Node.js

& Secure Authentication using JWTs

27-03-2019

About me

  • João Marques

 

  • Project Manager at KPMG

 

  • I've worked with Node.js for about 3 years

What is an API

  • Application Programming Interface

 

  • Abstraction of the underlying implementation

 

  • In the Web context is defined as a set of HTTP request and response messages, usually in JSON

 

  • The API dictates how other applications must interact with data

 

  • Usually allows basic data operations like CRUD

What is REST

  • The most popular API design pattern

 

  • Combines paths with verbs to describe the intended action

 

  • REST is not a protocol, but rather an architectural style

 

  • Hard to scale for very complex data models without careful planning

What is NodeJS

  • The most popular Javascript runtime engine outside the browser

 

  • Async, single threaded and event driven.

 

  • Optimal for handling a high amount of concurrent requests

 

  • Not great for CPU intensive work

 

  • Huge community support with lots of existing libraries

What is Express

  • The standard API framework for Node.js

 

  • Very simple to use

 

  • Abstracts all the sockets for matching routes, managing sockets, error handling, etc

 

  • Huge community support with lots of existing libraries

Getting started with Express

  • The express library is very simple to use

 

  • Just import the library and start it on any available port

 

import express from 'express'

const app = express()

app.get('/', (req, res) => res.send('My response'))

app.listen(3000, () => console.log('Example app listening on port 3000!'))

Middleware

  • Code that executes in order before reaching the end controller that handles the request

 

  • Great for common behavior such as authentication, error handling, logging, gzip compression, CORS, etc etc

 

  • All middlewares follow the same pattern
import express from 'express';

const myMiddleware = function(req, res, next) {
    // ... do whatever I need with req and res
    next() // ensure the next middleware in the chain is invoked
}

const app = express();
app.use(myMiddleware);

Exercise 1

In this exercise you'll be creating a simple Express based API in node that returns "Hello World" and logs the requests.

git clone https://gitlab.com/jmarques/api-design-nodejs.git
git checkout exercise-1
  1. install dependencies with npm
  2. create a route named 'api' that send an 'Hello World' response
  3. log each call to the console using the morgan logger (remember to install it with npm too!)
  4. start the server and test it

Complex Routing

  • Express supports very complex pattern matching with exact, regex, glob and parameter matching

 

  • Also supports HTTP verbs at route level (GET, POST, ...)

 

  • Routes are match in the order they are defined
app.get('/route', (req, res) => { ... })

app.get(/.*fly$/, (req, res) => { ... })

app.get('/route/:routeId', (req, res) => {
  // req.params: { routeId: '<value>' }
})

Exercise 2

In this exercise you'll be creating two different routes to access two different sets of data: posts and users

git checkout exercise-2
  1. create a route named 'api/users' that returns a list of users, and a route named 'api/users/:username' to get a specific user. Make use of the resources/users file to access the list of users
  2. create a route named 'api/posts' that returns a list of posts, and a route named 'api/posts/:id' to get a specific post. Make use of the resources/posts file to access the list of posts
  3. start the server and test it

Controllers and Routes

  • Controllers are just the final middleware in the stack, that will actually handle the request and return a response

 

  • Controllers handle a route + verb combination

 

  • You've been implementing controllers to handle the GET verb
app.get('/route', (req, res) => { ... })
app.post('/route', (req, res) => { ... })
app.patch('/route', (req, res) => { ... })

// OR

app.route('/route')
  .get((req, res) => { ... })
  .post((req, res) => { ... })
  .patch((req, res) => { ... })

Routers

  • Routers allow to create modular, mountable route handlers.

 

  • They have a complete middleware and routing system

 

  • Allows to encapsulate all logic that relates to a route

 

  • Can be nested, you can have Routers mounting other Routers

Routers

// in users.js
const router = express.Router()

router.get('/', (req, res) => { ... })
router.get('/:username', (req, res) => { ... })
router.post('/', (req, res) => { ... })

export default router

// in server.js
import userRouter from './users'

app.use('/users', userRouter')

Exercise 3

In this exercise you'll be expanding on the previous exercise to implement a full CRUD on two different sets of data: posts and users

git checkout exercise-3
npm i -S body-parser
  1. create a router named 'api/users' that handles all the methods available in resources/users
  2. create a router named 'api/posts' that handles all the methods available in resources/posts
  3. abstract the 'api' in its own router, using the routers created above
  4. start the server and test it
  5. BONUS: In POST responses, return the Location header in the response to indicate where the new resource is available

Authentication basics

  • Authentication is about controlling if an incoming request can have a response or not

 

  • Authorization is controlling if an authenticated request has the correct level of permissions to access a resource

 

  • Identification is determining who the requester is

 

  • You can never protect your API 100%

Authentication with JWTs

  • Based on passing a token (with every request) to check authentication on the server

 

  • Stateless authentication

 

  • Involves encoding a payload using a shared secret

 

  • After verification, the payload is available to the server. Payload can be used to store anything

Authentication with JWTs

  • jsonwebtoken is a package that handles both the creation and verification of JWTs

 

  • express-jwt is a package that simplifies the protection of routes with JWT
import jwt from 'jsonwebtoken'
import expressJwt from 'express-jwt'

const claims = {
  username: 'myUser',
  exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
}

// to generate a token
const token = jwt.sign(claims, 'my precious')
// to verify a token
const decoded = jwf.verify(token, 'my precious')

app.use('/myPath', expressJwt({ secret: 'my precious' }).unless({ path: <unprotected paths here> })

Exercise 4

In this exercise you'll be securing the application with JWT

git checkout exercise-4
npm i -S jsonwebtoken express-jwt bcrypt
  1. create a new route named '/login' that allows someone to login and obtain a token
  2. lock down all routes with the exception of creating a new user (signup)
  3. start the server and test it
  4. BONUS: make sure that editing or removing a user is done only if it is the user itself performing the request

Questions

Thank you

API Design in Node.js & Secure Authentication using JWTs

By João Marques

API Design in Node.js & Secure Authentication using JWTs

  • 148