How to add user authentication to your JavaScript app
A simple to follow guide on how to add user registration, login, and authentication to a JavaScript app, including example code.
Jun 10, 2025 • 15 Minute Read

- Before you build: Authorization vs authentication, explained
- Don't have an app yet for the tutorial?
- Tutorial prerequisites
- Setting up your project
- Adding user authentication to your app
- Deploying your authentication solution
- Exploring the app with user authentication
- Summary
- Further learning resources
In the dynamic landscape of web development, user authentication stands as a fundamental pillar of modern applications. Whether you're building a simple blog or a complex e-commerce platform, ensuring that only authorized users can access certain parts of your web application is paramount to both security and functionality.
In this post, I am going to take you through the process of adding user authentication to your JavaScript web application. We will explore integrating user registration, login, and authentication into your JavaScript application using Node.js and a MySQL database.
Before you build: Authorization vs authentication, explained
Going in it is important to understand the differences between user authorization and user authentication, as these are often mixed up when designing and architecting applications.
Authentication verifies who you are, like entering a username and password to log in.
Authorization controls what you can do after you’re logged in, such as viewing or editing content.
In this post we are going to keep it simple and focus on user authentication, as it must come first because authorization relies on knowing who the user is. If needed, you can do further research to learn how you would build out robust authorization controls into your JavaScript application for more granular control to various parts of your application.
Don't have an app yet for the tutorial?
This article assumes you have an existing application and are ready to add user authentication. However, if you don't have one, check out my Pluralsight course, “Building a Web Application with JavaScript”. My course is a part of the JavaScript 2022 path and walks you through building a basic JavaScript web application from scratch. Once you’ve built your app, you can come back to this blog post to learn how to add authentication.
Even if you’re starting a brand-new (greenfield) JavaScript app, this article will still be helpful. You can focus on the authentication parts and integrate them as you design and build your app.
For this article, I’ll be referencing the Dad Jokes web application in this GitHub repo. Feel free to clone or download the project and use it to follow along.
Tutorial prerequisites
Node.js and npm: Make sure you have Node.js and npm installed on your development machine. If not, you can download them from nodejs.org.
MySQL Database: Set up a MySQL database server, and note down the necessary credentials. If you're new to MySQL, you can install it locally or use a cloud-based service like Azure Database for MySQL, Amazon RDS or Google Cloud SQL.
Your JavaScript app: Either have your JavaScript web application ready or clone/download the Dad Jokes application.
Hosting for your app: You need somewhere to host your JavaScript web application and MySQL database. Your JavaScript web app and MySQL database can be deployed to the free tier on www.render.com with ease using these steps: Deploy a Node Express App to Render and steps for MySQL database hosting: Deploy MySQL.
Setting up your project
1. Set up your project directory
Clone the Dad Jokes application. You should end up with a directory structure like the following:
2. Set up the database
- Navigate to the dadjokes/setup directory called createdbandinsertjokes.
- Run against your MySQL DB: createDBandJokesTable.sql
- Run against your DB to create the jokes: insertJokes.sql
- Replace your_db_user, your_db_password, and your_db_name with your actual database credentials.
Adding user authentication to your app
In this section, we’ll walk through how to add simple login and signup functionality to an existing JavaScript app, specifically the Dad Jokes app. The goal is to require users to log in before they can edit jokes.
We’ll use MySQL to store user data and Bcrypt to securely hash passwords, all without introducing complex frameworks or external dependencies beyond Bcrypt. While there are many other technologies available for handling user authentication in JavaScript--- such as Firebase Authentication (Google), Auth0, Argon2, and NextAuth.js---using Bcrypt will help us keep things simple.
For those who are unfamiliar, Bcrypt is a library (available in JavaScript and many other languages) designed specifically for securely hashing and verifying passwords. It helps address the core security concern of storing user passwords.
Here’s what we’ll need:
- A signup page, a login page, and the ability to logout.
- A users table in our existing MySQL database to store user info.
- The jokelist.html page should only be accessible after a user logs in.
- Passwords must be securely hashed using Bcrypt.
By the end of this post our new authentication system should have the following functionality:
- Secure user registration with password hashing
- User login with session management
- Route protection for authenticated content
- Logout functionality
- Dynamic UI elements based on login status
- Error handling for unauthorized access
1. Modify the MySQL database
First we need to create a users table with fields like id, username, email, and hashed_password.
Here we create a SQL script named createUsersTable.sql in the createdbandinsertjokes folder with the following code in it:
USE DATABASENAMEHERE;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
hashed_password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
You can use MySQL Workbench to connect to your database and run the createUsersTable.sql script against your database. The MySQL Workbench tool is free and can be downloaded here.
In the SQL code, replace DATABASENAMEHERE with the name of your database.
After you run the SQL script, you should be see that the users table is created in your database as shown in the following screenshot:
2. Add Bcrypt dependency
Next we will add Bcrypt for the required hashing of passwords during signup and verifying them on login.
Here is where we need to install Bcrypt for password hashing. Run the following command in you’re terminal that has your directory open: npm install bcrypt
It will look similar to this:
3. Set up session management
We will use express-session to maintain the login state across requests.
Next, configure session management updating the index.js file adding the following code, and ensure you require express toward the beginning of the file:
// Essential middleware / dependencies
const express = require('express');
Then add the actual session middleware with this code:
// Session middleware
app.use(session({
secret: 'your_secret_key',
resave: true,
saveUninitialized: true,
cookie: { secure: false },
saveUninitialized: true,
cookie: { maxAge: 1800000 } // 30
}));
4. Create signup and login HTML pages
Now we want to create HTML files for the signup and login pages. In the features folder, create a login folder, and create a login.html file in it. Then, create a signup folder and create the signup.html file inside. You should have the following:
Here's the basic structure for each:
For features/signup/signup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Signup</title>
</head>
<body>
<div class="container">
<h1>Signup</h1>
<form id="signupForm" action="/japi/signup" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Signup</button>
</form>
<br>
<br>
<center>
<p>Already have an account? <a href="/features/login/login.html">Login here</a>.</p>
</center>
<center id="logout-container" style="display: none;">
<br>
<br>
<button id="simple-logout-btn" class="btn btn-danger">Logout</button>
<br>
<br>
</center>
<!-- logout button JavaScript -->
<script>
// Check if user is logged in when page loads
document.addEventListener('DOMContentLoaded', function() {
fetch('/check-auth')
.then(response => response.json())
.then(data => {
const logoutContainer = document.getElementById('logout-container');
if (data.loggedIn) {
// Show logout button if logged in
logoutContainer.style.display = 'block';
} else {
// Hide logout button if not logged in
logoutContainer.style.display = 'none';
}
})
.catch(error => {
console.error('Error checking auth status:', error);
});
});
// Logout button click handler
document.getElementById('simple-logout-btn').addEventListener('click', async function() {
console.log('Logout button clicked');
try {
const response = await fetch('/japi/logout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
if (response.ok) {
alert('Logged out successfully');
window.location.href = '/';
} else {
alert('Logout failed');
}
} catch (err) {
console.error('Logout error:', err);
}
});
</script>
<br>
<br>
<div class="container">
<center><a href="/" class="btn btn-primary">Home</a></center>
</div>
<script src="./signup.js"></script>
<br>
<br>
<footer>
<Center>
<p>© S. Buchanan</p>
</Center>
</footer>
</body>
</html>
For features/login/login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<div class="container">
<h1>Login</h1>
<form id="loginForm">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<br>
<br>
<center>
<p>Dont have an account? <a href="/features/signup/signup.html">Signup here</a>.</p>
</center>
<center id="logout-container" style="display: none;">
<br>
<br>
<button id="simple-logout-btn" class="btn btn-danger">Logout</button>
<br>
<br>
</center>
<!-- logout button JavaScript -->
<script>
// Check if user is logged in when page loads
document.addEventListener('DOMContentLoaded', function() {
fetch('/check-auth')
.then(response => response.json())
.then(data => {
const logoutContainer = document.getElementById('logout-container');
if (data.loggedIn) {
// Show logout button if logged in
logoutContainer.style.display = 'block';
} else {
// Hide logout button if not logged in
logoutContainer.style.display = 'none';
}
})
.catch(error => {
console.error('Error checking auth status:', error);
});
});
// Logout button click handler
document.getElementById('simple-logout-btn').addEventListener('click', async function() {
console.log('Logout button clicked');
try {
const response = await fetch('/japi/logout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
if (response.ok) {
alert('Logged out successfully');
window.location.href = '/';
} else {
alert('Logout failed');
}
} catch (err) {
console.error('Logout error:', err);
}
});
</script>
<br>
<br>
<div class="container">
<center><a href="/" class="btn btn-primary">Home</a></center>
</div>
<script src="./login.js"></script>
<br>
<br>
<footer>
<Center>
<p>© S. Buchanan</p>
</Center>
</footer>
</body>
</html>
5. Style the new signup and login pages with CSS
For a consistent UX update the signup and login HTML pages with CSS. We need to add the code for bootstrap CSS that is used in the other HTML pages. This code is:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
We need to add this code in after the page title <title>Login</title> before </head>. It should look like this:
You may have noticed in the HTML code that we already have code for the various buttons we will need. Note that I used Bootstrap CSS in my app. You may use something different and style things in a different way. What I have is just an example for you to follow if you want.
6. Create signup and login JavaScript files
Handle the form submission and communicate with the backend.
Next let’s add some signup.js and login.js JavaScript files to handle form submissions and communicate with the backend. We will create these in the signup and login folders. Here are the files we need and their code:
For features/signup/signup.js
document.getElementById('signupForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const response = await fetch('/japi/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, email, password })
});
if (response.ok) {
alert('Signup successful! Redirecting to login...');
window.location.href = '/features/login/login.html';
} else {
const error = await response.json();
alert(`Signup failed: ${error.message || 'Please try again.'}`);
}
});
For features/login/login.js
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const response = await fetch('/japi/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
if (response.ok) {
alert('Login successful! Redirecting to jokes list...');
window.location.href = '/features/jokelist/jokelist.html';
} else {
alert('Login failed. Please check your credentials.');
}
});
7. Add authentication-related routes in apiserver.js
Now let’s add the /signup, /login, and /logout routes to the apiserver.js file to handle user authentication. These routes will use Bcrypt for password hashing and verification. Add the following code:
// Essential middleware / dependencies
const express = require('express');
const bcrypt = require('bcrypt');
const session = require('express-session');
const db = require('./config/dbinfo');
const router = express.Router();
//Signup route
router.post('/signup', async (req, res) => {
const { username, email, password } = req.body;
try {
const hashedPassword = await bcrypt.hash(password, 10);
const query = 'INSERT INTO users (username, email, hashed_password) VALUES (?, ?, ?)';
db.query(query, [username, email, hashedPassword], (err) => {
if (err) {
console.error(err);
return res.status(500).send('Error creating user');
}
res.status(201).send('User created');
});
} catch (error) {
res.status(500).send('Error hashing password');
}
});
// Login route
router.post('/login', async (req, res) => {
const { username, password } = req.body;
console.log('Login attempt for:', username);
const query = 'SELECT * FROM users WHERE username = ?';
db.query(query, [username], async (err, results) => {
if (err) {
console.error('Database error:', err);
return res.status(500).json({ message: 'Server error' });
}
if (results.length === 0) {
console.log('User not found');
return res.status(401).json({ message: 'Invalid username or password' });
}
const user = results[0];
console.log('User found, comparing passwords');
try {
const match = await bcrypt.compare(password, user.hashed_password);
console.log('Password match result:', match);
if (match) {
req.session.userId = user.id;
req.session.username = user.username;
// Don't try to set session directly from router
// Instead, return user info to be handled in index.js
console.log('Login successful for user:', username);
return res.status(200).json({
message: 'Login successful',
user: {
id: user.id,
username: user.username
}
});
} else {
return res.status(401).json({ message: 'Invalid username or password' });
}
} catch (error) {
console.error('Error comparing passwords:', error);
return res.status(500).json({ message: 'Server error' });
}
});
});
// Logout route
router.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).send('Error logging out');
}
res.status(200).send('Logout successful');
});
});
8. Add authentication middleware to protect sensitive routes/pages
With the app, we want to allow only logged-in users to access the jokelist.html page. This page displays a list of all the jokes and provides options to edit or delete them, so it’s important to protect it behind authentication. Here’s what the page looks like:
To protect sensitive routes, we need to add middleware that checks if a user is logged in before granting access. Once this is in place, the jokelist.html route will be secured using the isAuthenticated middleware, ensuring only logged-in users can access it.
In this first block of code we are adding custom middleware that extends the response object with a new method called res.unauthorizedRedirect(). This method stores an optional message in the session and then redirects the user to the login page.
// Custom middleware that uses the Session middleware
app.use((req, res, next) => {
// Adds custom method to response object
res.unauthorizedRedirect = function(message) {
// Stores the message in session
req.session.authError = message;
// Redirects to login page
res.redirect('/features/login/login.html');
};
next();
});
We use this next body of code to serve both protected and non-protected pages and static files in the app.
The first group of app.use() statements serves non-protected routes such as the homepage, signup, login, add joke, and logout pages.
The last app.use() statement serves the jokelist feature, but only if the user is authenticated. It uses the isAuthenticated middleware to restrict access before serving the files in that route.
// Serve Non-Protected routes / files / pages of the app
app.use(express.static(path.join(__dirname, 'views')));
app.use('/features/signup', express.static(path.join(__dirname, 'features/signup')));
app.use('/features/login', express.static(path.join(__dirname, 'features/login')));
app.use('/features/addjoke', express.static(path.join(__dirname, 'features/addjoke')));
app.use('/features/logout', express.static(path.join(__dirname, 'features/logout')));
// Serve Protected routes / files / pages of the app
app.use('/features/jokelist', isAuthenticated, express.static(path.join(__dirname, 'features/jokelist')));
Now we need to add middleware to check whether a user is authenticated. This function will determine if the user is allowed to access the jokelist.html page based on whether their session contains a valid user ID.
// Middleware to check if the user is authenticated
function isAuthenticated(req, res, next) {
console.log('Session check:', req.session);
console.log('User ID in session:', req.session.userId);
if (req.session && req.session.userId) {
console.log('Authentication successful for user ID:', req.session.userId);
return next();
}
console.log('Authentication failed - redirecting to login');
res.unauthorizedRedirect('Unauthorized: Please log in to access this page.');
}
Next, we add a route to check if the user is authenticated, and another route to return any error message stored from an unauthorized access attempt.
The /check-auth route checks whether a user is logged in by looking for a userId in the session. If found, it responds with loggedIn: true and the username. If not, it returns loggedIn: false.
The /check-auth-error route is used to retrieve any authentication error message (for example, after a failed login or unauthorized redirect). After returning the error, it clears it from the session.
// Authentication check route
app.get('/check-auth', (req, res) => {
if (req.session.userId) {
res.json({ loggedIn: true, username: req.session.username });
} else {
res.json({ loggedIn: false });
}
});
// Error Handling system for unauthorized access
app.get('/check-auth-error', (req, res) => {
const authError = req.session.authError;
// Clear the error after sending it
req.session.authError = null;
res.json({ authError });
});
Lastly, we include a route for logging out the user. This is part of our session management. The /japi/logout route handles logout requests by destroying the current session. If successful, it responds with a confirmation message. If there’s an error during logout, it logs the error and returns a server error response.
app.post('/japi/logout', (req, res) => {
console.log('Logout request received');
// Destroy the session
req.session.destroy((err) => {
if (err) {
console.error('Error during logout:', err);
return res.status(500).json({ message: 'Error logging out' });
}
console.log('Session destroyed successfully');
// Send success response
res.status(200).json({ message: 'Logout successful' });
});
});
9. Add UI navigation elements Show/Hide Login and Logout buttons
Next, we’ll update the HTML pages (signup, login, addjoke, jokelist, and index.html) to include navigation options for signup, login, and logout. We’ll also add logic to show or hide the login and logout buttons based on whether the user is currently logged in.
We start by updating index.html, addjoke.html, and jokelist.html with a navigation header that includes links to the signup and login pages, along with a logout button:
<header>
<nav>
<a href="/features/signup/signup.html">Signup</a>
<a href="/features/login/login.html">Login</a>
<button id="logoutButton" style="display: none;">Logout</button>
</nav>
</header>
Now let’s add Navigation links between the Signup and Login pages. In signup.html, add a link to navigate to the login page using the following code:
<p>Already have an account? <a href="/features/login/login.html">Login here</a>.</p>
</body>
</html>
In login.html, add a link to navigate to the signup page:
<p>Don't have an account? <a href="/features/signup/signup.html">Signup here</a>.</p>
</body>
</html>
Now lets add a visible Logout button. To add a more prominent logout button, include this block of code wherever you want the button to appear:
<center id="logout-container" style="display: none;">
<br>
<br>
<button id="simple-logout-btn" class="btn btn-danger">Logout</button>
<br>
<br>
<br>
<br>
<div class="container">
<a href="/" class="btn btn-primary">Home</a></center>
</div>
Now let’s add JavaScript to handle the show/hide and logout functionality. Add the following JavaScript script to your HTML pages to manage the visibility of the logout button and handle logout functionality:
<!-- logout button JavaScript -->
<script>
// Check if user is logged in when page loads
document.addEventListener('DOMContentLoaded', function() {
fetch('/check-auth')
.then(response => response.json())
.then(data => {
const logoutContainer = document.getElementById('logout-container');
if (data.loggedIn) {
// Show logout button if logged in
logoutContainer.style.display = 'block';
} else {
// Hide logout button if not logged in
logoutContainer.style.display = 'none';
}
})
.catch(error => {
console.error('Error checking auth status:', error);
});
});
// Logout button click handler
document.getElementById('simple-logout-btn').addEventListener('click', async function() {
console.log('Logout button clicked');
try {
const response = await fetch('/japi/logout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
if (response.ok) {
alert('Logged out successfully');
window.location.href = '/';
} else {
alert('Logout failed');
}
} catch (err) {
console.error('Logout error:', err);
}
});
</script>
Deploying your authentication solution
Your app is now updated and ready to be deployed to the hosting platform of your choice, such as Heroku, Azure, AWS, DigitalOcean, or others. As mentioned earlier, Render.com is also a solid option. Be sure to configure environment variables for any sensitive data, such as your database credentials and secret key.
Exploring the app with user authentication
If you're using the Dad Jokes app from the course as your base, you now have a more secure version ready for users. Below is how the application should look.
Home page:
Adding a joke:
Sign up:
Login:
Logout:
Some of the pages after logged in:
Summary
By following this blog post, you have successfully added user registration, login, and authentication to the Dad Jokes JavaScript app built with Node.js and a MySQL database. Remember to adapt the code and implementation details to suit your specific project's needs and requirements if you are using a different JavaScript application.
With the signup and login routes, protected pages, session handling, and a user table in place, your app now has a solid foundation for user management. You can continue building on this by adding features like password resets, email verification, role-based access control, or integrating with OAuth providers in the future.
If you need a full working copy of this Dad Jokes JavaScript web app you can find the GitHub repository here. You could clone this repo and deploy it from there or use it for reference. Thanks for reading!
Further learning resources
Want to learn more about building cool things with JavaScript? Check out Steve Buchanan's JavaScript learning courses below.
- Building a REST API in JavaScript with Express
- Building a Web Application with JavaScript
- JavaScript Performance Playbook
Alternatively, check out Pluralsight's full JavaScript 2022 learning path, which coveres everything from JavaScript fundamentals to advanced solution architecture techniques.
Advance your tech skills today
Access courses on AI, cloud, data, security, and more—all led by industry experts.