Home  >  Article  >  Web Front-end  >  A secure authentication in nodejs with mongodb database.

A secure authentication in nodejs with mongodb database.

DDD
DDDOriginal
2024-11-05 11:19:02611browse

A secure authentication in nodejs with mongodb database.

Node.js Package Reference Guide

Core Dependencies

Web Framework & Server

  • express (^4.18.2)
    • Web application framework
    • Handles routing, middleware, and HTTP requests/responses
    • Core foundation for building APIs and web applications

Database

  • mongoose (^7.0.0)
    • MongoDB object modeling tool
    • Provides schema-based solution to model application data
    • Handles database operations and relationships

Security Packages

  • jsonwebtoken (^9.0.0)

    • Creates and verifies JSON Web Tokens (JWT)
    • Used for user authentication and secure information exchange
  • bcryptjs (^2.4.3)

    • Hashes and compares passwords securely
    • Protects user passwords in the database
  • helmet (^6.0.1)

    • Adds security headers to HTTP responses
    • Protects against common web vulnerabilities
    • Sets various HTTP headers for security
  • cors (^2.8.5)

    • Enables Cross-Origin Resource Sharing
    • Controls which domains can access your API
    • Essential for web applications with separate frontend/backend

Validation & Configuration

  • joi (^17.9.0)

    • Data validation library
    • Validates request bodies, query parameters, and other inputs
    • Ensures data integrity and format
  • dotenv (^16.0.3)

    • Loads environment variables from .env file
    • Manages configuration settings
    • Keeps sensitive data secure

Development Dependencies

Development Tools

  • nodemon (^2.0.22)
    • Monitors file changes during development
    • Automatically restarts the server
    • Improves development workflow

Usage Example

// Express server setup
const express = require('express');
const app = express();

// Security middleware
const helmet = require('helmet');
const cors = require('cors');
app.use(helmet());
app.use(cors());

// Environment variables
require('dotenv').config();

// Database connection
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGODB_URI);

// Validation example
const Joi = require('joi');
const schema = Joi.object({
    email: Joi.string().email().required()
});

// Password hashing
const bcrypt = require('bcryptjs');
const hashedPassword = await bcrypt.hash('password', 10);

// JWT authentication
const jwt = require('jsonwebtoken');
const token = jwt.sign({userId: 123}, process.env.JWT_SECRET);

Complete Node.js and Mongoose Project Structure Guide

Project Structure

project-root/
├── src/
│   ├── config/
│   │   ├── database.js
│   │   └── config.js
│   ├── models/
│   │   ├── user.model.js
│   │   └── product.model.js
│   ├── controllers/
│   │   ├── user.controller.js
│   │   └── product.controller.js
│   ├── routes/
│   │   ├── user.routes.js
│   │   └── product.routes.js
│   ├── middleware/
│   │   ├── auth.middleware.js
│   │   └── error.middleware.js
│   ├── utils/
│   │   ├── logger.js
│   │   └── validators.js
│   └── app.js
├── .env
├── .gitignore
└── package.json

1. Initial Setup

package.json

{
  "name": "node-mongoose-project",
  "version": "1.0.0",
  "main": "src/app.js",
  "scripts": {
    "start": "node src/app.js",
    "dev": "nodemon src/app.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.0.0",
    "dotenv": "^16.0.3",
    "joi": "^17.9.0",
    "jsonwebtoken": "^9.0.0",
    "bcryptjs": "^2.4.3",
    "cors": "^2.8.5",
    "helmet": "^6.0.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.22"
  }
}

.env

PORT=3000
MONGODB_URI=mongodb://localhost:27017/your-database
JWT_SECRET=your-secret-key
NODE_ENV=development

.gitignore

node_modules/
.env
logs/
*.log

2. Configuration Setup

src/config/config.js

require('dotenv').config();

module.exports = {
    port: process.env.PORT || 3000,
    mongoUri: process.env.MONGODB_URI,
    jwtSecret: process.env.JWT_SECRET,
    nodeEnv: process.env.NODE_ENV || 'development',
    jwtExpiresIn: '1d'
};

src/config/database.js

const mongoose = require('mongoose');
const config = require('./config');
const logger = require('../utils/logger');

const connectDB = async () => {
    try {
        await mongoose.connect(config.mongoUri, {
            useNewUrlParser: true,
            useUnifiedTopology: true
        });
        logger.info('MongoDB connected successfully');
    } catch (error) {
        logger.error('MongoDB connection error:', error);
        process.exit(1);
    }
};

module.exports = connectDB;

3. Models Definition

src/models/user.model.js

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('../config/config');

const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: [true, 'Name is required'],
        trim: true,
        minlength: 3,
        maxlength: 50
    },
    email: {
        type: String,
        required: [true, 'Email is required'],
        unique: true,
        lowercase: true,
        trim: true
    },
    password: {
        type: String,
        required: [true, 'Password is required'],
        minlength: 6,
        select: false
    },
    role: {
        type: String,
        enum: ['user', 'admin'],
        default: 'user'
    }
}, {
    timestamps: true
});

// Pre-save middleware to hash password
userSchema.pre('save', async function(next) {
    if (!this.isModified('password')) return next();
    this.password = await bcrypt.hash(this.password, 12);
    next();
});

// Instance methods
userSchema.methods.generateAuthToken = function() {
    return jwt.sign(
        { id: this._id, role: this.role },
        config.jwtSecret,
        { expiresIn: config.jwtExpiresIn }
    );
};

userSchema.methods.comparePassword = async function(candidatePassword) {
    return await bcrypt.compare(candidatePassword, this.password);
};

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

src/models/product.model.js

const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        trim: true
    },
    price: {
        type: Number,
        required: true,
        min: 0
    },
    description: String,
    category: {
        type: String,
        required: true
    },
    createdBy: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User',
        required: true
    }
}, {
    timestamps: true
});

const Product = mongoose.model('Product', productSchema);
module.exports = Product;

4. Controllers

src/controllers/user.controller.js

const User = require('../models/user.model');
const { validateUser } = require('../utils/validators');
const logger = require('../utils/logger');

exports.register = async (req, res) => {
    try {
        const { error } = validateUser(req.body);
        if (error) return res.status(400).json({ error: error.details[0].message });

        const user = await User.create(req.body);
        const token = user.generateAuthToken();

        res.status(201).json({
            status: 'success',
            token,
            data: { user }
        });
    } catch (error) {
        logger.error('Registration error:', error);
        res.status(400).json({
            status: 'fail',
            message: error.message
        });
    }
};

exports.login = async (req, res) => {
    try {
        const { email, password } = req.body;

        const user = await User.findOne({ email }).select('+password');
        if (!user || !(await user.comparePassword(password))) {
            return res.status(401).json({
                status: 'fail',
                message: 'Invalid email or password'
            });
        }

        const token = user.generateAuthToken();
        res.json({
            status: 'success',
            token
        });
    } catch (error) {
        logger.error('Login error:', error);
        res.status(400).json({
            status: 'fail',
            message: error.message
        });
    }
};

5. Routes

src/routes/user.routes.js

const express = require('express');
const router = express.Router();
const userController = require('../controllers/user.controller');
const auth = require('../middleware/auth.middleware');

router.post('/register', userController.register);
router.post('/login', userController.login);
router.get('/profile', auth, userController.getProfile);

module.exports = router;

6. Middleware

src/middleware/auth.middleware.js

const jwt = require('jsonwebtoken');
const config = require('../config/config');
const User = require('../models/user.model');

module.exports = async (req, res, next) => {
    try {
        const token = req.headers.authorization?.replace('Bearer ', '');

        if (!token) {
            return res.status(401).json({
                status: 'fail',
                message: 'No token provided'
            });
        }

        const decoded = jwt.verify(token, config.jwtSecret);
        const user = await User.findById(decoded.id);

        if (!user) {
            return res.status(401).json({
                status: 'fail',
                message: 'User not found'
            });
        }

        req.user = user;
        next();
    } catch (error) {
        res.status(401).json({
            status: 'fail',
            message: 'Invalid token'
        });
    }
};

src/middleware/error.middleware.js

const logger = require('../utils/logger');

module.exports = (err, req, res, next) => {
    logger.error(err.stack);

    if (err.name === 'ValidationError') {
        return res.status(400).json({
            status: 'fail',
            message: err.message
        });
    }

    if (err.code === 11000) {
        return res.status(400).json({
            status: 'fail',
            message: 'Duplicate field value'
        });
    }

    res.status(err.status || 500).json({
        status: 'error',
        message: err.message || 'Internal server error'
    });
};

7. Utils

src/utils/logger.js

const winston = require('winston');
const config = require('../config/config');

const logger = winston.createLogger({
    level: config.nodeEnv === 'development' ? 'debug' : 'info',
    format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
        new winston.transports.File({ filename: 'logs/combined.log' })
    ]
});

if (config.nodeEnv === 'development') {
    logger.add(new winston.transports.Console({
        format: winston.format.simple()
    }));
}

module.exports = logger;

src/utils/validators.js

const Joi = require('joi');

exports.validateUser = (user) => {
    const schema = Joi.object({
        name: Joi.string().min(3).max(50).required(),
        email: Joi.string().email().required(),
        password: Joi.string().min(6).required(),
        role: Joi.string().valid('user', 'admin')
    });

    return schema.validate(user);
};

exports.validateProduct = (product) => {
    const schema = Joi.object({
        name: Joi.string().required(),
        price: Joi.number().min(0).required(),
        description: Joi.string(),
        category: Joi.string().required()
    });

    return schema.validate(product);
};

8. Main Application File

src/app.js

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const config = require('./config/config');
const connectDB = require('./config/database');
const errorMiddleware = require('./middleware/error.middleware');
const userRoutes = require('./routes/user.routes');
const productRoutes = require('./routes/product.routes');
const logger = require('./utils/logger');

// Initialize express app
const app = express();

// Connect to MongoDB
connectDB();

// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);

// Error handling
app.use(errorMiddleware);

// Start server
app.listen(config.port, () => {
    logger.info(`Server running in ${config.nodeEnv} mode on port ${config.port}`);
});

// Handle unhandled promise rejections
process.on('unhandledRejection', (err) => {
    logger.error('UNHANDLED REJECTION! Shutting down...');
    logger.error(err.name, err.message);
    process.exit(1);
});

Running the Application

  1. Install dependencies:
npm install
  1. Set up your environment variables in .env

  2. Start the development server:

npm run dev
  1. For production:
npm start

Testing the API

Test the API endpoints using tools like Postman or curl:

# Register a new user
curl -X POST http://localhost:3000/api/users/register \
  -H "Content-Type: application/json" \
  -d '{"name": "John Doe", "email": "john@example.com", "password": "password123"}'

# Login
curl -X POST http://localhost:3000/api/users/login \
  -H "Content-Type: application/json" \
  -d '{"email": "john@example.com", "password": "password123"}'

This structured guide provides a complete setup for a Node.js and Mongoose application with proper organization, error handling, and security features. Each file has its specific responsibility, making the codebase maintainable and scalable.

The above is the detailed content of A secure authentication in nodejs with mongodb database.. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Previous article:TCP vs UDP protocolNext article:TCP vs UDP protocol