Express.js Authentication
Authentication is a crucial part of web applications to verify the identity of users. Express.js provides various strategies to implement authentication, including sessions, tokens, and OAuth. This guide covers key concepts, examples, and best practices for implementing authentication in Express.js applications.
Key Concepts of Authentication
- Authentication: The process of verifying the identity of a user.
- Authorization: The process of determining what resources a user has access to.
- Sessions: A mechanism to maintain user state across multiple requests using cookies.
- Tokens: JSON Web Tokens (JWT) are used to secure APIs by ensuring only authenticated users can access them.
- OAuth: An open standard for access delegation commonly used for token-based authentication.
Setting Up Authentication with Passport.js
Passport.js is a popular authentication middleware for Node.js, supporting a wide range of authentication strategies.
Installation
npm install passport passport-local express-session bcryptjs --save
Example: Setting Up Passport.js with Local Strategy
// app.js
const express = require('express');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');
const bcrypt = require('bcryptjs');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
// In-memory user store for example purposes
const users = [];
// Middleware setup
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
// Configure passport-local strategy
passport.use(new LocalStrategy((username, password, done) => {
const user = users.find(user => user.username === username);
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!bcrypt.compareSync(password, user.password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
}));
passport.serializeUser((user, done) => {
done(null, user.username);
});
passport.deserializeUser((username, done) => {
const user = users.find(user => user.username === username);
done(null, user);
});
// Routes
app.get('/', (req, res) => {
res.send('Home Page');
});
app.get('/login', (req, res) => {
res.send('');
});
app.post('/login', passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: false
}));
app.get('/dashboard', (req, res) => {
if (req.isAuthenticated()) {
res.send('Dashboard - Authenticated User: ' + req.user.username);
} else {
res.redirect('/login');
}
});
app.get('/register', (req, res) => {
res.send('');
});
app.post('/register', (req, res) => {
const { username, password } = req.body;
const hashedPassword = bcrypt.hashSync(password, 10);
users.push({ username, password: hashedPassword });
res.redirect('/login');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Using JSON Web Tokens (JWT) for Authentication
Installation
npm install jsonwebtoken bcryptjs --save
Example: JWT Authentication
// jwt-auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
const users = [];
const secretKey = 'secretkey';
// Middleware setup
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Routes
app.post('/register', (req, res) => {
const { username, password } = req.body;
const hashedPassword = bcrypt.hashSync(password, 10);
users.push({ username, password: hashedPassword });
res.send('User registered');
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(user => user.username === username);
if (!user) {
return res.status(400).send('Invalid username or password');
}
if (!bcrypt.compareSync(password, user.password)) {
return res.status(400).send('Invalid username or password');
}
const token = jwt.sign({ username: user.username }, secretKey, { expiresIn: '1h' });
res.json({ token });
});
const authenticateJWT = (req, res, next) => {
const token = req.headers.authorization;
if (token) {
jwt.verify(token, secretKey, (err, user) => {
if (err) {
return res.sendStatus(403);
}
req.user = user;
next();
});
} else {
res.sendStatus(401);
}
};
app.get('/dashboard', authenticateJWT, (req, res) => {
res.send(`Dashboard - Authenticated User: ${req.user.username}`);
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Using OAuth with Passport.js
Installation
npm install passport passport-google-oauth20 express-session --save
Example: Google OAuth Authentication
// google-auth.js
const express = require('express');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const session = require('express-session');
const app = express();
const port = 3000;
// Configure Passport.js
passport.use(new GoogleStrategy({
clientID: 'GOOGLE_CLIENT_ID',
clientSecret: 'GOOGLE_CLIENT_SECRET',
callbackURL: 'http://localhost:3000/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj, done) => {
done(null, obj);
});
// Middleware setup
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
// Routes
app.get('/', (req, res) => {
res.send('Authenticate with Google');
});
app.get('/auth/google',
passport.authenticate('google', { scope: ['https://www.googleapis.com/auth/plus.login'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/' }),
(req, res) => {
res.redirect('/dashboard');
}
);
app.get('/dashboard', (req, res) => {
if (req.isAuthenticated()) {
res.send(`Dashboard - Authenticated User: ${req.user.displayName}`);
} else {
res.redirect('/');
}
});
app.get('/logout', (req, res) => {
req.logout();
res.redirect('/');
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Best Practices for Authentication
- Use Secure Password Storage: Always hash passwords before storing them in the database using libraries like bcrypt.
- Validate Inputs: Validate and sanitize user inputs to prevent injection attacks.
- Use HTTPS: Ensure all authentication-related data is transmitted over HTTPS to prevent man-in-the-middle attacks.
- Token Expiration: Implement token expiration and refresh mechanisms to enhance security.
- Error Handling: Implement robust error handling for authentication processes.
Testing Authentication
Test your authentication logic using frameworks like Mocha, Chai, and Supertest:
Example: Testing Authentication
// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest
// test/auth.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const app = express();
const secretKey = 'secretkey';
const users = [];
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.post('/register', (req, res) => {
const { username, password } = req.body;
const hashedPassword = bcrypt.hashSync(password, 10);
users.push({ username, password: hashedPassword });
res.send('User registered');
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(user => user.username === username);
if (!user) {
return res.status(400).send('Invalid username or password');
}
if (!bcrypt.compareSync(password, user.password)) {
return res.status(400).send('Invalid username or password');
}
const token = jwt.sign({ username: user.username }, secretKey, { expiresIn: '1h' });
res.json({ token });
});
describe('POST /register', () => {
it('should register a new user', (done) => {
request(app)
.post('/register')
.send({ username: 'John', password: 'password' })
.expect(200, 'User registered', done);
});
});
describe('POST /login', () => {
it('should log in a user and return a token', (done) => {
request(app)
.post('/login')
.send({ username: 'John', password: 'password' })
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) return done(err);
expect(res.body).to.have.property('token');
done();
});
});
it('should return 400 for invalid credentials', (done) => {
request(app)
.post('/login')
.send({ username: 'invalid', password: 'invalid' })
.expect(400, 'Invalid username or password', done);
});
});
// Define test script in package.json
// "scripts": {
// "test": "mocha"
// }
// Run tests with NPM
// npm run test
Key Points
- Authentication: The process of verifying the identity of a user.
- Authorization: The process of determining what resources a user has access to.
- Sessions: A mechanism to maintain user state across multiple requests using cookies.
- Tokens: JSON Web Tokens (JWT) are used to secure APIs by ensuring only authenticated users can access them.
- Follow best practices for authentication, such as using secure password storage, validating inputs, using HTTPS, implementing token expiration, and handling errors properly.
Conclusion
Authentication is a crucial part of web applications to verify the identity of users. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively implement authentication in your Express.js applications. Happy coding!