Introduction
User authentication is a critical component of modern web applications. It ensures that users can securely register, log in, and access protected resources. Implementing authentication correctly is essential for protecting user data, preventing unauthorized access, and maintaining trust with your users.
In this post, we will guide you through the initial setup of user authentication in a Node.js application. You will learn how to create a registration and login system, handle user data securely, and prepare your application for implementing password storage and session management. While this post focuses on the foundational setup, future posts can build upon this to implement advanced features like JWT-based authentication, OAuth, and multi-factor authentication.
Prerequisites
Before we start, make sure you have the following installed and ready:
- Node.js: Ensure that Node.js is installed by running
node -v
. - npm: Node Package Manager should be available to install required libraries.
- Express.js: A web framework for Node.js to handle HTTP requests.
- Database: Any database can be used to store user credentials. For this tutorial, we will use MongoDB with Mongoose for simplicity.
- Postman or Browser: To test API endpoints.
Step 1: Setting Up a Node.js Project
1.1 Initialize a New Project
Create a new directory for your project and initialize it as a Node.js application:
mkdir auth-app
cd auth-app
npm init -y
This will create a package.json
file with default settings.
1.2 Install Required Dependencies
We need several packages for handling HTTP requests, routing, database interaction, and user input:
npm install express mongoose body-parser bcryptjs express-session
- express: Web framework for handling HTTP requests.
- mongoose: ODM to interact with MongoDB.
- body-parser: Middleware to parse incoming request bodies.
- bcryptjs: Library to hash passwords securely.
- express-session: Middleware for session management.
Step 2: Setting Up Express Server
Create a file named app.js
to configure your Express server.
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const session = require('express-session');
const app = express();
// Middleware
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Session setup
app.use(session({
secret: 'mysecretkey', // Change this to a secure secret
resave: false,
saveUninitialized: true
}));
// MongoDB connection
mongoose.connect('mongodb://localhost:27017/authDB', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => console.log('MongoDB connected'))
.catch(err => console.error(err));
// Start the server
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
Explanation
bodyParser.urlencoded
andbodyParser.json
are used to parse incoming form data and JSON data.express-session
is initialized to manage user sessions.- MongoDB is connected via Mongoose.
Step 3: Creating a User Model
To store user information, we will create a Mongoose schema and model. Create a folder called models
and inside it a file User.js
:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
});
const User = mongoose.model('User', userSchema);
module.exports = User;
Explanation
username
andemail
are required and must be unique.password
will be stored as a hashed string in a later step.
Step 4: Registration Endpoint
Next, we will create a route for user registration. This route will accept user input, validate it, hash the password, and save the user in the database.
const express = require('express');
const bcrypt = require('bcryptjs');
const User = require('./models/User');
const router = express.Router();
// Registration route
router.post('/register', async (req, res) => {
const { username, email, password } = req.body;
// Simple validation
if (!username || !email || !password) {
return res.status(400).send('All fields are required');
}
try {
// Check if user already exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).send('Email already registered');
}
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
// Create new user
const newUser = new User({
username,
email,
password: hashedPassword
});
await newUser.save();
res.status(201).send('User registered successfully');
} catch (err) {
console.error(err);
res.status(500).send('Server error');
}
});
module.exports = router;
Explanation
- Validation: Ensures all required fields are provided.
- Email Check: Prevents duplicate registration.
- Password Hashing: Uses bcrypt to hash passwords securely.
- User Creation: Saves the user into MongoDB.
Step 5: Login Endpoint
The login endpoint will verify user credentials and start a session.
// Login route
router.post('/login', async (req, res) => {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).send('Email and password are required');
}
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(400).send('Invalid credentials');
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(400).send('Invalid credentials');
}
// Start session
req.session.user = user._id;
res.send('Login successful');
} catch (err) {
console.error(err);
res.status(500).send('Server error');
}
});
Explanation
- The email and password are validated.
- The password is compared with the stored hashed password using bcrypt.
- On successful login, a session is created to track the logged-in user.
Step 6: Protecting Routes
Once users are authenticated, you may want to restrict access to certain routes. You can create middleware to check if the user is logged in.
function isAuthenticated(req, res, next) {
if (req.session.user) {
return next();
} else {
return res.status(401).send('Unauthorized');
}
}
// Example protected route
router.get('/dashboard', isAuthenticated, (req, res) => {
res.send('Welcome to your dashboard');
});
Explanation
isAuthenticated
middleware checks for a valid session.- Routes using this middleware are protected from unauthorized access.
Step 7: Logout Endpoint
To allow users to log out, you can destroy the session.
router.get('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).send('Server error');
}
res.send('Logged out successfully');
});
});
Explanation
req.session.destroy()
ends the session and logs the user out.
Step 8: Organizing Your Application
For scalability, structure your project like this:
auth-app/
├─ app.js
├─ routes/
│ └─ auth.js
├─ models/
│ └─ User.js
└─ package.json
app.js
: Initializes the server and connects to MongoDB.models/User.js
: Contains the user schema.routes/auth.js
: Contains registration, login, logout, and protected routes.
In app.js
, use the router:
const authRoutes = require('./routes/auth');
app.use('/api/auth', authRoutes);
Step 9: Testing the Authentication System
- Use Postman to test the API endpoints:
POST /api/auth/register
to create a new user.POST /api/auth/login
to log in.GET /api/auth/dashboard
to access a protected route.GET /api/auth/logout
to log out.
- Verify that sessions are created and destroyed properly.
- Check the database to ensure hashed passwords are stored correctly.
Step 10: Security Considerations
- Use HTTPS: Always use HTTPS in production to encrypt traffic.
- Environment Variables: Store sensitive data like session secrets in
.env
files. - Password Hashing: Use a strong hashing algorithm like bcrypt.
- Session Management: Set secure and HTTP-only cookies for sessions.
- Input Validation: Validate all user input to prevent injection attacks.
Leave a Reply