DEV Community

loading...
Cover image for Serverless Login with OpenJS Architect, Part 2

Serverless Login with OpenJS Architect, Part 2

Paul Chin Jr.
Curious Human. #Serverless #JavaScript Dev @ http://arc.codes; Dev Rel at http://begin.com; Prophet of Nicolas Cage; #PraiseCage
Updated on ・3 min read

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.arcfile

Update your app.arc file to the following:

@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
Enter fullscreen mode Exit fullscreen mode

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.

// src/shared/auth.js

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

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.

// 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

async function logout() {
  return {
    session: {},
    location: '/'
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

// 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'
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

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!

Discussion (0)