To implement user registration and login, first, we will create an auth page that will house the two forms that we will use for registration and login. We can create the form view in the views folder and name it auth.ejs. Insert the following content into the file:
<%- include( 'partials/header.ejs' ) -%>
<div class="columns">
<div class="column">
<h1 class="title">Register</h1>
<form action="/auth/register" method="POST">
<div class="field">
<label class="label">Full Name</label>
<div class="control">
<input class="input" type="text" placeholder="e.g Alex Smith"
name="fullName">
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="e.g.
alexsmith@gmail.com" name="email">
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" name="password">
</div>
</div>
<div class="control">
<button class="button is-primary">Submit</button>
</div>
</form>
</div>
<div class="column">
<h1 class="title">Login</h1>
<form action="/auth/login" method="POST">
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="e.g.
alexsmith@gmail.com" name="email">
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" name="password">
</div>
</div>
<div class="control">
<button class="button is-primary">Submit</button>
</div>
</form>
</div>
</div>
<%- include( 'partials/footer.ejs' ) -%>
Now that we have our forms created, let's create the controller actions for serving and handling the forms. We will create an AuthController file in the controllers folder to hold all the actions needed. Insert the following content into the file:
// ./controllers/AuthController.js
const User = require('../models/User');
const bcrypt = require('bcrypt');
const BCRYPT_SALT_ROUNDS = 12;
module.exports = {
async index(ctx) {
ctx.state = { title: 'Login or Register' };
await ctx.render('auth');
},
async register(ctx) {
const { body } = ctx.request;
const userData = {
...body,
password: await bcrypt.hash(body.password, BCRYPT_SALT_ROUNDS)
};
const user = await new User(userData).save();
ctx.session.user = user;
ctx.redirect('/');
},
async login(ctx) {
const { body } = ctx.request;
const user = await User.findOne({ email: body.email });
if (!user) ctx.throw(404, 'user not found');
const isValid = await bcrypt.compare(body.password, user.password);
if (isValid) {
ctx.session.user = user;
ctx.redirect('/');
} else {
ctx.redirect('/auth');
}
},
async logout(ctx) {
delete ctx.session.user;
ctx.redirect('/auth');
}
};
The controller created contains three methods that are given here:
- index: This loads the auth page and shows the register and login forms.
- register: This handles registration by doing the following:
- Getting the user data submitted in the form.
- Generating a hash of the user password using the bcrypt library.
- Saving the user to the database.
- Setting the user in the current session.
- Redirecting the user to the home page.
- login: This handles user login by doing the following:
- Getting the user data submitted via the form.
- Retrieving a user matching the email address from the database.
- Comparing the password supplied to the one in the database using the bcrypt library.
- Setting the user in the current session if the password supplied is correct.
- Redirecting the user based on the correctness of the password supplied.
- logout: This removes the user object from the session object. This means our application will no longer recognize the user as logged in.
Next, we register these actions in our router.js file:
// ./middleware/router.js
// ...
// we are passing the title variable to the view now
router.get('/', ctx => ctx.render('index', { title: 'Welcome' }));
const authController = require('../controllers/AuthController');
// auth routes
const auth = new KoaRouter()
.get('/', authController.index)
.post('/login', authController.login)
.post('/register', authController.register)
.get('/logout', authController.logout);
router.use('/auth', auth.routes());
// ...
In the preceding code block, we create a new router instance for the auth routes and register them as sub-routes under our main router using router.use('/auth', auth.routes()). This means that all the auth routes will be accessible under the '/auth' group.
We will also make changes to our header file to show our page title, include a link to the login/register page, and show the current user if the person is logged in. Update the header file as shown:
<!-- ./views/partials/header.ejs -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= title %> - KoaBlog</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css">
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
<section class="section">
<div class="container">
<div class="columns">
<div class="column">
<h1 class="title"><a href="/">KoaBlog</a></h1>
<p class="subtitle">Blog built with Koa!</p>
</div>
<div class="column">
<div class="has-text-grey-dark has-text-right">
<% if (locals.user) { %>
<p>
Hi, <%= user.fullName %>. <a
href="/auth/logout">Logout</a><br>
</p>
<% } else { %>
<p><a href="/auth">Login/Register</a></p>
<% } %>
</div>
</div>
</div>
<hr>
Note that the user variable used in the preceding template has not yet been passed to the view. We will do that in the next step when we define authentication middleware.