Serverless Login with OpenJS Architect, Part 2
Tags: serverless, node, aws
Published: 2020-10-29 16:25:34

In Part 1 of this series, we covered the basics of getting the application started and we left off with needing to build the authenticated route. We're naming the route `/admin` with a function `get-admin` behind it to provide a secure route that only a logged-in user should be able to see. We're also going to need to build a logout function that clears the session object from the response. 

## Creating the restricted page
The first time a user sees a protected page is immediately following registration. `post-register` redirects the user to `/admin` along with an `account` object on the user's HTTP session. The next steps include adding new routes, creating an auth middleware, and implementing a logout function to clear the HTTP session. 

## Adding routes by modifying your `app.arc`file

Update your `app.arc` file to the following: 
```bash
@app
@app
login-flow

@http
get /
get /register
post /register
get /admin
get /logout
get /login
post /login

@tables
data
  scopeID *String
  dataID **String
  ttl TTL
```

## Auth middleware
We will create an auth middleware function and place it in `/src/shared` this is similar to the `/src/views` but its contents are copied to every Lambda Function's `node_modules` folder. We can then just require it at the top of the function and pass it as the first argument to `arc.http.async` and it will behave like Express style middleware. 

```javascript
// src/shared/auth.js

module.exports = function(req) {
  if(!req.session.account) {
    return {
      location: '/?authorized=false'
    }
  }
}
```

## Rendering the protected route

The `get-admin` function is responsible for creating the admin view after checking that the user's session is valid. We'll also need to install `@architect/functions`.

```javascript
// src/http/get-admin/index.js

let arc = require('@architect/functions')
let auth = require('@architect/shared/auth')
let layout = require('@architect/views/layout')

// run auth middleware first, then admin function
exports.handler = arc.http.async(auth, admin)
async function admin(req) {
  let html = layout({
    account: req.session.account,
    body: `
    <p>This is protected.</p>
    <form action=/register/nuke method=post>
        Nuke your account
      <input name=email type=email placeholder="add your email" required>
      <input name=password type=password required>
      <button>Nuke</button>
    </form>
    `
  })
  return {
    html
  }
}
```
## Logging out

A user logs out when their session is cleared. We can achieve this with a `get-logout` function. Feels pretty clean so far.

```javascript
// src/http/get-logout/index.js
let arc = require('@architect/functions')

exports.handler = arc.http.async(logout)

async function logout() {
  return {
    session: {},
    location: '/'
  }
}
```

## Logging in

Logging in will take two routes, `get-login` and `post-login`. The GET route will render an HTML Form and POST data to the `post-login` Lambda function. A reminder that this new function will also need `@architect/functions` installed in the folder. 

```javascript
// src/http/get-login/index.js

let arc = require('@architect/functions')
let layout = require('@architect/views/layout')

exports.handler = arc.http.async(login)

let loginForm = `
  <form action=/login method=post>
    <input name=email type=email placeholder="add your email" required>
    <input name=password type=password required>
    <button> Login </button>
  </form>
`

async function login(req) {
  return {
    html: layout({
      account: req.session.account,
      body: loginForm
    })
  }
}
```
Now we can work on the `post-login` function. It may seem redundant to install dependencies per function, but it keeps the individual units of work separate and speedy. Especially at deploy time. You can deploy individual functions without updating the entire system at the same time. 

```javascript
// src/http/post-login/index.js

let arc = require('@architect/functions')
let data = require('@begin/data')
let bcrypt = require('bcryptjs')

exports.handler = arc.http.async(login)

async function login(req) {

  let result = await data.get({
    table: 'accounts',
    key: req.body.email
  })

  if(!result) {
    return {
      session: {},
      location: '/?notfound'
    }
  }

  let hash = result.password
  console.log(hash)
  let good = bcrypt.compareSync(req.body.password, hash)

  if(good) {
    return {
      session: {
        account: {
          email: req.body.email
        }
      },
      location: '/admin'
    }
  } else {
    return {
      session: {},
      location: '/?badpassword'
    }
  }
}

```
Up to this point, we should have a functioning app that has registration, password hashing, session support for protecting routes, logging in, and logging out. Not bad. The next post will cover some extra muscle that serverless can offer. 

## Next time: Asynchronous event functions

Asynchronous event functions! In the next article, we'll go over triggering a verification email from SendGrid to verify your new account!