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:
@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)