Web Authentication By The Numbers (Part 1)
Published: 2021-11-17 20:30:09

## How authentication layers are built up to create an authentication system for your website.

## Audience
This article is intended for intermediate level website developers and software engineers. I've tried to make it approachable for beginners but it's really not for absolute beginners.

## Video
{% youtube BBIlu1x_E3g %}

## Problem
Website authentication can be a very confusing topic. There are a lot of considerations when thinking about an authentication system for your web projects. It's overwhelming because authentication can be extremely simple or it can be a layer cake of individual systems that each build on top of each other.

## Approach
In this series we're going to start with no authentication and then you'll add a very basic authentication system. Then you'll progressively add and remove layers on top of it to make it a full blown Authentication System for your website using PassportJS. 

Once that is done you'll learn how to go one step further and implement Xumm (a crypto currency wallet) SignIn as a stand-in for a traditional `user:password` based authentication mechanism. We'll do this using a new PassportJS strategy I've created to authenticate your users with Xumm.

For the grand finale you'll learn how to implement all of this in BlitzJS by executing a single line of code using a BlitzJS recipe.

## Assumptions
The examples here use localhost without Transport Layer Security. I'll assume you understand that all this is not secure in a real world production environment without [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security).

### For early portions of this series I'll assume
1. You're familiar with Typescript. 
1. You know how to setup [NodeJS](https://nodejs.dev/) and [ExpressJS](https://expressjs.com/).
1. You're familiar with the concept of [middleware for ExpressJS](https://expressjs.com/en/guide/using-middleware.html).
1. You know how to use [Postman](https://www.postman.com/) to make calls to your application as if you're a client.
1. You're familiar with [PassportJS](http://www.passportjs.org/) but may not have implemented it previously. 

### For late portions of this series I'll assume
1. You understand that [Xumm](https://xumm.app/) is a wallet for the [XRP Ledger](https://xrpl.org/) (an open source crypto currency project).
1. You're familiar with [BlitzJS](https://blitzjs.com/).

## GitHub Repo
If you want to follow along with examples there is a branch for each type of authentication system we're building here over on my corresponding [Web Authentication By The Numbers Github Repo](https://github.com/jfgrissom/web-authentication-by-the-numbers).

{% github jfgrissom/web-authentication-by-the-numbers %}

## Starting With No Authentication (Step 0)
Initially we'll start the application on the master branch where there is no authentication. See the [`index.ts`](https://github.com/jfgrissom/web-authentication-by-the-numbers/blob/master/index.ts) file on the master branch for this.

```typescript
import express, { Application, Request, Response } from 'express'

const app: Application = express()
const port = 3000

app.get('/', async (req: Request, res: Response): Promise<Response> => {
  return res.status(200).send({
    message: "Hello World! I'm not authenticated."
  })
})

app.listen(port)
```
Making a call to this using Postman will return this.

```
{
    "message": "Hello World! I'm not authenticated."
}
```

## Primitive Authentication System (Step 1)
Probably the most primitive authentication system we can build with express contains a simple set of hard coded credentials. Using this [basic auth example](https://github.com/jfgrissom/web-authentication-by-the-numbers/blob/feature/basic-auth/index.ts) we can setup some thing like this.

_**NOTE: This authentication system is horrible for many reasons. Don't use this in your app (the user and password will be checked into Github). This example is just to help you understand what is going on here.**_

```typescript
import express, { Application, Request, Response, NextFunction } from 'express'
import auth from 'basic-auth'

const app: Application = express()
const port = 3000

app.use((req: Request, res: Response, next: NextFunction) => {
  let user = auth(req)

  if (
    user === undefined ||
    user['name'] !== 'admin' ||
    user['pass'] !== 'supersecret'
  ) {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
    res.end('Unauthorized')
  } else {
    next()
  }
})

app.get('/', async (req: Request, res: Response): Promise<Response> => {
  return res.status(200).send({
    message: "Hello World! I'm authenticated."
  })
})

app.listen(port)
```
Once you get `basicAuth` added to your application you can try to make a call to the service using Postman but you'll just get an empty response with a status code of `401 Unauthorized`.

To get an authenticated response you'll need to setup credentials in the "Authorization" tab of your Postman request. The Username is "admin" and the Password is "supersecret". 

Make the request again with these credentials and you'll get this for a response.

```
{
    "message": "Hello World! I'm authenticated."
}
```
At this point you've got a password database and you can accept "Basic Authentication Headers" from any client.

The user database can be much more complicated than this. It could be in a database or provided by an external authentication provider (like [AWS Cognito](https://aws.amazon.com/cognito/)). For now we'll leave it simple and just keep using `basicAuth`.

## Session Support (Step 2)
So providing credentials every time someone requests something from your site is OK if the client is an API consumer (like another web service). However this isn't typically how you would handle authentication for users who show up to your site using a web browser.

So what resources will you need to create to provide this functionality?
1. At this point you'll need to provide some webpage features that allow a user to login, use authorized resources, and logout.
2. You'll also need something that won't require them to login every time they click on something within the page.

Let's begin by adding session support to the project.

To see the code for this take a look at the [session support branch](https://github.com/jfgrissom/web-authentication-by-the-numbers/blob/feature/session-support/index.ts) of the repo.

_**NOTE: This branch intentionally doesn't have authentication in it.**_

```typescript
import express, { Application, Request, Response } from 'express'
import session from 'express-session'

const app: Application = express()
const port = 3000

const sessionOptions = {
  secret: 'session secret that is not secret'
}

app.use(session(sessionOptions))

app.get('/', async (req: Request, res: Response): Promise<Response> => {
  return res.send(`Session ID: ${req.session.id}`)
})

app.listen(port)
```
Once you've updated this file connect to your site using a web browser at `http://localhost:3000/`. When you do this you should see a result similar to this on your web page `Session ID: Outbyq2G_EYkL5VQzAdKlZIZPYfaANqB`.

_**NOTE: To keep your browser sessions secure in production you would not share this session ID over an unsecured connection. You would use https (TLS).**_

So what is this session good for exactly? I'm glad you asked! This session is your server's way of keeping track of browser sessions (note it doesn't take care of user sessions - at least not yet anyway). The session solves the problem of requiring a user to login every time they click on something within the page.

So you've got a session and you've got a user database. How exactly  do these things tie together?

The session is tied to a specific client (in this case a browser). The way the server and browser share data related to this session is through a cookie. If you look at the cookies in your browser you'll see that it matches the ID that was presented in your web page.

## Session Support with User Support (Step 3)
So how to the session and the user tie together?

In this example we'll reintroduce the Basic Authentication feature by merging in the two previous branches we created ([feature/basic-auth](https://github.com/jfgrissom/web-authentication-by-the-numbers/blob/feature/basic-auth) and [feature/session-support](https://github.com/jfgrissom/web-authentication-by-the-numbers/blob/feature/session-support)).

You should end up with with this after accounting for previously existing sessions. See the [code here](https://github.com/jfgrissom/web-authentication-by-the-numbers/blob/feature/session-with-user/index.ts). 

```typescript
import express, { Application, Request, Response, NextFunction } from 'express'
import session from 'express-session'
import auth from 'basic-auth'

// Add the session data we need that is specific to our application.
declare module 'express-session' {
  interface SessionData {
    userToken?: string
    tokenExpiration?: number
  }
}

const app: Application = express()
const port = 3000

const sessionOptions = {
  secret: 'session secret that is not secret',
  cookie: {
    httpOnly: true // Only let the browser modify this, not JS.
  }
}

app.use(session(sessionOptions))

app.use((req: Request, res: Response, next: NextFunction) => {
  // If we have a previous session with key session data then we are authenticated.
  const currentTime = Date.now() / 1000
  if (
    req.session.userToken &&
    req.session.tokenExpiration &&
    req.session.tokenExpiration > currentTime
  ) {
    next()
    return
  }

  // If no prior session was established and bad credentials were passed.
  const user = auth(req)
  if (
    user === undefined ||
    user['name'] !== 'admin' ||
    user['pass'] !== 'supersecret'
  ) {
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Basic realm="Node"')
    res.end('Unauthorized')
    return
  }

  // Create a new session for the user who has passed good credentials.
  req.session.userToken = user.name
  req.session.tokenExpiration = currentTime + 15 // 15 second session.
  next()
})

app.get('/', async (req: Request, res: Response): Promise<Response> => {
  const currentTime = Date.now() / 1000
  return res.send(`
  Session ID: ${req.session.id} <br/>
  Authenticated Username: ${auth(req)?.name} <br/>
  User Token: ${req.session.userToken} <br/>
  Current Time: ${currentTime} <br/>
  Session Expiration: ${req.session.tokenExpiration}
  `)
})

app.listen(port)
```
You have session functionality and you have basic authentication functionality. 

You can test how the page behaves without credentials by going to the page in a web browser and clicking cancel when prompted for a username and password. You should see a `401 Error` in the console and `unauthorized` on the web page.

You can test how the page behaves with credentials by prepending the username and password in the url so that it looks like this `http://admin:supersecret@localhost:3000/`.

```
Session ID: Wc29HPGVTdnx0VqsDr7uaxWPTV3KoIzO
Authenticated Username: admin
User Token: admin
Current Time: 1637179009.834
Session Expiration: 1637179024.829
```

You can test out the session persistence by refreshing the page. You'll notice that the User Token remains admin but the Authenticated Username becomes `undefined`.

To test out the session expiring by passing good credentials like this `http://admin:supersecret@localhost:3000/`. Then you can pass  bad bad credentials to the page like this `http://bad:credentials@localhost:3000/`. Then refresh the page repeatedly until the session expires after 15 seconds. When the tokenExpires then you'll see a prompt show up for the Username and Password (just click cancel). _**NOTE: This is most easily done in Chrome because it will not automatically cache (and reuse) good credentials after you've passed bad credentials.**_

With this latest iteration we've answered a few questions.
1. How do we access the name of the user? You can see the Authenticated username came in through the authenticated request `auth(req)` and that if we want to use it again we'll need to access it through the session.
1. How does our system know if the user previously was authenticated? It knows because a prior session was established.
1. Why can't a browser just manipulate the cookie and add data we are expecting? We are telling browsers that they can't make changes to the cookie using Javascript with the httpOnly directive `{cookie: { httpOnly: true }}`. Our server knows the state of the cookie and will reject it if the client changes the cookie.

So what if you don't want to use Basic Auth? This is a very reasonable thing. Basic auth is pretty terrible for a lot of reasons.

I've added more to this in the next section of this series. The section is called [Web Authentication By the Numbers (Part 2)](https://dev.to/jfgrissom/web-authentication-by-the-numbers-part-2-2855) and it deals directly with setting up PassportJS using the Local Strategy.

## Article Image Credit
Photo by <a href="https://unsplash.com/@parsoakhorsand?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Parsoa Khorsand</a> on <a href="https://unsplash.com/s/photos/lock?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>