W05 Learning Activity: User Login
Overview
In this activity, you will learn how to create a login system that checks a user's credentials and saves that information to the session to remember who is logged in.
As part of that process, you will build login functionality that verifies user passwords, creates sessions, and protects routes that require a logged-in user. By the end of this activity, you will have a working login system for your application.
Preparation Material
Authentication
As you recall from the previous learning activity on Authentication and Authorization, authentication is the process of verifying who a user is.
In your case, the user provides an email and password to prove who they are. The server then checks if those credentials are correct. If they are correct, the server creates a session to remember that the user is logged in.
The Login Process
When a user submits a login form, several steps happen:
- The server receives the email and password from the form.
- The server looks up the user in the database using the email.
- The server compares the submitted password with the stored password.
- If the passwords match, the server creates a session for the user.
- The server stores the user information (not including the password hash) in the session.
Password Security with bcrypt
As you have learned previously, passwords should never be stored in plain text in the database. Instead, they are stored as hashed values. The bcrypt library has a compare function that can check if a submitted password matches the hashed password stored in the database. This keeps passwords secure even if someone gains access to the database.
Sessions and Authentication
A session is a way for the server to remember information about a user between different page requests. When a user logs in successfully, their user information is stored in the session. The server can then check the session on future requests to see if the user is logged in.
Protecting Routes
Some pages in your application should only be available to logged-in users. You will create middleware that checks if a user is logged in. Middleware is code that runs before your route handlers and can block access if the user is not logged in. This middleware will check the session for user information and redirect unauthorized users to the login page.
Activity Instructions
Create Model Functions for User Authentication
You need to create functions in your model that will handle finding users and checking passwords. Complete the following:
- Open your user model file
src/models/users.js. - Create a function named
findUserByEmailthat accepts an email address as a parameter and returns the user from the database with that email. This function should look as follows:const findUserByEmail = async (email) => { const query = ` SELECT user_id, name, email, password_hash, role_id FROM users WHERE email = $1 `; const query_params = [email]; const result = await db.query(query, query_params); if (result.rows.length === 0) { return null; // User not found } return result.rows[0]; }; - Create a function named
verifyPasswordthat accepts a plain text password and a hashed password as parameters. It then usesbcrypt.compare()to check if they match. Return true if they match, false if they do not. Your function should look as follows:const verifyPassword = async (password, passwordHash) => { return bcrypt.compare(password, passwordHash); }; - Create a function named
authenticateUserthat takes an email and password as parameters. This function should:- Use
findUserByEmailto get the user. - If no user is found, return null.
- Use
verifyPasswordto check if the password is correct. - If the password is correct, remove the
password_hashfrom the user object and return the user object. If not, return null.
- Use
- The only function the controller needs access to is the
authenticateUserfunction, so make sure to export it, but you do not need to export the other two functions.
Create Controller Functions for Login
Now you need to create controller functions to handle showing the login form and processing the login. Complete the following:
- Open your
src/controllers/users.jscontroller file. - Import the
authenticateUserfunction from your user model. - Create a function called
showLoginFormthat renders the login view. - Create a function called
processLoginFormthat does the following:- Gets the email and password from the request body.
- Calls
authenticateUserwith the email and password. - Check to see if a user object is returned. If so:
- Add the user object to the session object:
req.session.user = user;. - Add a
successflash message that the login was successful. - Add a
console.log()statement to log the user in the console for debugging purposes. - Redirect to the home page.
- Add the user object to the session object:
- If authentication fails (the function returns null):
- Add an
errorflash message that the login failed. - Redirect the user back to the login page.
- Add an
- Create a function called
processLogoutthat does the following:- Destroys the session using
req.session.destroy() - Adds a
successflash message indicating the user has logged out. - Redirects the user to the login page.
- Destroys the session using
- Export all three functions.
Sample Code (click to expand)
const showLoginForm = (req, res) => {
res.render('login', { title: 'Login' });
};
const processLoginForm = async (req, res) => {
const { email, password } = req.body;
try {
const user = await authenticateUser(email, password);
if (user) {
// Store user info in session
req.session.user = user;
req.flash('success', 'Login successful!');
if (res.locals.NODE_ENV === 'development') {
console.log('User logged in:', user);
}
res.redirect('/');
} else {
req.flash('error', 'Invalid email or password.');
res.redirect('/login');
}
} catch (error) {
console.error('Error during login:', error);
req.flash('error', 'An error occurred during login. Please try again.');
res.redirect('/login');
}
};
const processLogout = async (req, res) => {
if (req.session.user) {
delete req.session.user;
}
req.flash('success', 'Logout successful!');
res.redirect('/login');
};
Create Routes for Login
Complete the following:
- Open your routes file
src/controllers/routes.js. - Import the login controller functions you created.
- Add a GET route for
/loginthat calls theshowLoginFormfunction. - Add a POST route for
/loginthat calls theprocessLoginFormfunction. - Add a GET route for
/logoutthat calls theprocessLogoutfunction.
Sample Code (click to expand)
// User login routes
router.get('/login', showLoginForm);
router.post('/login', processLoginForm);
router.get('/logout', processLogout);
Create the Login View
You need to create a view that displays a login form. Complete the following:
- In your
src/viewsfolder, create a new view file calledlogin.ejs. - Add a form with the method set to POST and the action set to your login route
- Inside the form, add:
- An input field for email with type="email" and name="email"
- An input field for password with type="password" and name="password"
- A submit button
- Add labels for each input field to make the form clear
Sample Code (click to expand)
<%- include('partials/header') %>
<main>
<h1><%= title %></h1>
<form action="/login" method="POST">
<div class="form-group">
<label for="email">Email (Username)</label>
<input
type="email"
id="email"
name="email"
required
/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
id="password"
name="password"
required
/>
</div>
<button type="submit">Login</button>
</form>
</main>
<%- include('partials/footer') %>
Stop here and test your login functionality
At this point, you should be able to log in to your application. There will not be any restrictions on accessing routes yet or even anything different in the user interface, but you should be able to see the flash message indicating success.
By taking a minute to verify this part of the process first, you can ensure that the basic login mechanism and password hashing features are working correctly before moving on to more complex features.
Add Variable for UI display
Many parts of the UI will display differently depending on whether a user is logged in or not. To make this easy for the Views, you will add a variable to the response object that indicates the login status of the user. This variable can then be used in your templates to render UI elements.
- Open you
server.jsfile. - Find the middleware function that made the NODE_ENV variable available on the response object. It should look something like this:
app.use((req, res, next) => { res.locals.NODE_ENV = NODE_ENV; next(); }); - Add code to this function to set a new variable
res.locals.isLoggedInthat istrueifreq.session.userexists, otherwisefalse. - The function should now look as follows:
app.use((req, res, next) => { res.locals.isLoggedIn = false; if (req.session && req.session.user) { res.locals.isLoggedIn = true; } res.locals.NODE_ENV = NODE_ENV; next(); });
Enhance Application with Login State
Now that you have login functionality working and a way to check if a user is logged in, you will update your navigation to display a login or logout button depending one whether the user is logged in or not.
- Open your header partial view file
src/views/partials/header.ejs. - Previously, you added a Register link to the navigation. Now, update as follows:
- If the user is logged in: Include a link to the logout route (
/logout) - If the user is not logged in: Include both the register link and a link to the login form (
/login)
- If the user is logged in: Include a link to the logout route (
Sample Code (click to expand)
<% if (isLoggedIn) { %>
<li><a href="/register">Register</a></li>
<li><a href="/login">Login</a></li>
<% } else { %>
<li><a href="/logout">Logout</a></li>
<% } %>
Test Your Login Functionality
Now you need to test that everything works correctly. Complete the following:
- Start your server and navigate to the login page in your browser
- Try logging in with incorrect credentials and verify that you see an error message
- Log in with correct credentials and verify that:
- You are redirected to the appropriate page
- The navigation shows a logout link
- Navigate to different pages and make sure the session persists (you should continue to see the logout link).
- Log out and verify that the navigation shows login and register links
- Fix any errors you find
Testing Tip
Use your browser's developer tools to check the network tab and see what data is being sent when you submit the login form. You can also check the application tab to see session cookies.
Next Step
Complete the other Week 05 Learning Activities
After you have completed all the learning activities for this lesson, return to Canvas to submit a quiz.
Other Links:
- Return to: Week Overview | Course Home