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
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'
}
}
}
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
}
}
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: '/'
}
}
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
})
}
}
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'
}
}
}
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!
Top comments (0)