Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Express.js and Advanced Authentication

Advanced authentication techniques ensure the security and integrity of your Express.js applications. This guide covers key concepts, examples, and best practices for implementing advanced authentication in Express.js applications.

Key Concepts of Advanced Authentication

  • JSON Web Tokens (JWT): A compact, URL-safe means of representing claims to be transferred between two parties.
  • OAuth 2.0: An authorization framework that enables applications to obtain limited access to user accounts on an HTTP service.
  • Passport.js: A middleware for authentication in Node.js that supports various authentication strategies.
  • Multi-Factor Authentication (MFA): An additional layer of security requiring multiple verification methods.
  • Role-Based Access Control (RBAC): Restricting system access to authorized users based on their roles.

Setting Up the Project

Initialize a new Express.js project and install necessary dependencies:

// Initialize a new project
// npm init -y

// Install Express and authentication libraries
// npm install express jsonwebtoken passport passport-jwt bcryptjs

// Create the project structure
// mkdir src config routes middleware
// touch src/index.js config/passport.js routes/auth.js middleware/auth.js .gitignore

// .gitignore
node_modules
.env

Setting Up Passport.js with JWT

Configure Passport.js to use JWT for authentication:

Example: passport.js

// config/passport.js
const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const User = require('../models/User'); // Assume a User model is defined

const opts = {
    jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    secretOrKey: process.env.JWT_SECRET
};

passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
    User.findById(jwt_payload.id)
        .then(user => {
            if (user) {
                return done(null, user);
            }
            return done(null, false);
        })
        .catch(err => console.error(err));
}));

module.exports = passport;

Creating User Model and Authentication Routes

Create a user model and authentication routes to handle user registration, login, and JWT issuance:

Example: User.js

// models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
    name: String,
    email: { type: String, unique: true },
    password: String
});

UserSchema.pre('save', async function(next) {
    if (this.isModified('password')) {
        this.password = await bcrypt.hash(this.password, 10);
    }
    next();
});

UserSchema.methods.comparePassword = function(password) {
    return bcrypt.compare(password, this.password);
};

module.exports = mongoose.model('User', UserSchema);

Example: auth.js

// routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const passport = require('passport');
const router = express.Router();

router.post('/register', async (req, res) => {
    try {
        const { name, email, password } = req.body;
        const user = new User({ name, email, password });
        await user.save();
        res.status(201).send('User registered');
    } catch (error) {
        res.status(400).send(error.message);
    }
});

router.post('/login', async (req, res) => {
    try {
        const { email, password } = req.body;
        const user = await User.findOne({ email });
        if (!user || !(await user.comparePassword(password))) {
            return res.status(401).send('Invalid credentials');
        }
        const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
        res.json({ token });
    } catch (error) {
        res.status(400).send(error.message);
    }
});

router.get('/profile', passport.authenticate('jwt', { session: false }), (req, res) => {
    res.json({ user: req.user });
});

module.exports = router;

Setting Up the Express Application

Configure the Express application to use the authentication routes and middleware:

Example: index.js

// src/index.js
const express = require('express');
const mongoose = require('mongoose');
const passport = require('passport');
const authRoutes = require('../routes/auth');
require('dotenv').config();
require('../config/passport');

const app = express();
const port = process.env.PORT || 3000;

app.use(express.json());
app.use(passport.initialize());
app.use('/api/auth', authRoutes);

mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => console.log('MongoDB connected'))
    .catch(err => console.error(err));

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Implementing Multi-Factor Authentication (MFA)

Integrate MFA into your authentication process using TOTP (Time-Based One-Time Password):

Example: MFA Implementation

// Install speakeasy for TOTP
// npm install speakeasy

// models/User.js (add TOTP secret)
const speakeasy = require('speakeasy');

const UserSchema = new mongoose.Schema({
    name: String,
    email: { type: String, unique: true },
    password: String,
    totpSecret: String
});

// routes/auth.js (add MFA routes)
router.post('/enable-mfa', passport.authenticate('jwt', { session: false }), async (req, res) => {
    const secret = speakeasy.generateSecret();
    req.user.totpSecret = secret.base32;
    await req.user.save();
    res.json({ secret: secret.otpauth_url });
});

router.post('/verify-mfa', passport.authenticate('jwt', { session: false }), (req, res) => {
    const { token } = req.body;
    const verified = speakeasy.totp.verify({
        secret: req.user.totpSecret,
        encoding: 'base32',
        token
    });
    if (verified) {
        res.send('MFA verified');
    } else {
        res.status(400).send('Invalid token');
    }
});

Implementing Role-Based Access Control (RBAC)

Restrict system access based on user roles:

Example: RBAC Implementation

// models/User.js (add roles)
const UserSchema = new mongoose.Schema({
    name: String,
    email: { type: String, unique: true },
    password: String,
    role: { type: String, default: 'user' } // Add roles
});

// middleware/auth.js
const authorize = (roles = []) => {
    return (req, res, next) => {
        if (!roles.includes(req.user.role)) {
            return res.status(403).send('Forbidden');
        }
        next();
    };
};

module.exports = authorize;

// routes/auth.js (add RBAC to routes)
router.get('/admin', passport.authenticate('jwt', { session: false }), authorize(['admin']), (req, res) => {
    res.send('Admin access');
});

Best Practices for Advanced Authentication

  • Use HTTPS: Ensure secure communication by using HTTPS.
  • Hash Passwords: Hash passwords using a secure algorithm (e.g., bcrypt) before storing them.
  • Implement Token Expiry: Set an expiry time for JWT tokens to enhance security.
  • Enable MFA: Add an additional layer of security by implementing Multi-Factor Authentication.
  • Use Role-Based Access Control: Restrict access to resources based on user roles.
  • Keep Secrets Secure: Store secret keys and sensitive information in environment variables.
  • Monitor and Log Authentication Events: Keep track of authentication attempts and events for security audits.

Testing Advanced Authentication

Test your authentication implementation to ensure it works correctly and securely:

Example: Testing with Mocha and Chai

// Install Mocha and Chai
// npm install --save-dev mocha chai

// test/auth.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const app = require('../src/index');
const User = require('../models/User');

describe('Authentication', () => {
    before(async () => {
        await User.deleteMany({});
    });

    it('should register a user', (done) => {
        request(app)
            .post('/api/auth/register')
            .send({ name: 'John', email: 'john@example.com', password: 'password' })
            .expect(201, done);
    });

    it('should log in a user and return a JWT token', (done) => {
        request(app)
            .post('/api/auth/login')
            .send({ email: 'john@example.com', password: 'password' })
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                expect(res.body).to.have.property('token');
                done();
            });
    });

    it('should access protected route with valid token', (done) => {
        request(app)
            .post('/api/auth/login')
            .send({ email: 'john@example.com', password: 'password' })
            .end((err, res) => {
                if (err) return done(err);
                const token = res.body.token;
                request(app)
                    .get('/api/auth/profile')
                    .set('Authorization', `Bearer ${token}`)
                    .expect(200, done);
            });
    });
});

// Add test script to package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests
// npm test

Key Points

  • JSON Web Tokens (JWT): A compact, URL-safe means of representing claims to be transferred between two parties.
  • OAuth 2.0: An authorization framework that enables applications to obtain limited access to user accounts on an HTTP service.
  • Passport.js: A middleware for authentication in Node.js that supports various authentication strategies.
  • Multi-Factor Authentication (MFA): An additional layer of security requiring multiple verification methods.
  • Role-Based Access Control (RBAC): Restricting system access to authorized users based on their roles.
  • Follow best practices for advanced authentication, such as using HTTPS, hashing passwords, implementing token expiry, enabling MFA, using RBAC, keeping secrets secure, and monitoring and logging authentication events.

Conclusion

Advanced authentication techniques ensure the security and integrity of your Express.js applications. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively implement advanced authentication in your Express.js applications. Happy coding!