akash-coded / mern

Learn MERN stack concepts with hands-on coding
Apache License 2.0
162 stars 35 forks source link

This will be a basic app where users can register, log in, and view a protected resource using JWT for authentication. #200

Open imwirelesscloud opened 1 year ago

imwirelesscloud commented 1 year ago

This will be a basic app where users can register, log in, and view a protected resource using JWT for authentication.

Backend Setup:

1. Initialize a new Node.js project:

mkdir mern-jwt-demo
cd mern-jwt-demo
npm init -y

2. Install necessary packages:

npm install express mongoose bcryptjs jsonwebtoken cors

3. Setup MongoDB:

4. Create a .env file for environment variables:

MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_secret_key

5. Setup Express and MongoDB:

server.js:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

const app = express();

// Middleware
app.use(express.json());
app.use(cors());

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

const PORT = 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

6. Create User model:

models/User.js:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
    username: { type: String, required: true, unique: true },
    password: { type: String, required: true },
});

// Hash password before saving
userSchema.pre('save', async function(next) {
    if (this.isModified('password')) {
        this.password = await bcrypt.hash(this.password, 10);
    }
    next();
});

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

7. Create routes for registration and login:

routes/auth.js:

const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const router = express.Router();

// Register
router.post('/register', async (req, res) => {
    const { username, password } = req.body;

    // Check if user exists
    const userExists = await User.findOne({ username });
    if (userExists) return res.status(400).send('User already exists');

    const user = new User({ username, password });
    await user.save();

    res.send('User registered');
});

// Login
router.post('/login', async (req, res) => {
    const { username, password } = req.body;

    const user = await User.findOne({ username });
    if (!user) return res.status(400).send('Invalid credentials');

    const validPassword = await bcrypt.compare(password, user.password);
    if (!validPassword) return res.status(400).send('Invalid credentials');

    const token = jwt.sign({ _id: user._id }, process.env.JWT_SECRET);
    res.header('auth-token', token).send(token);
});

module.exports = router;

8. Add routes to server.js:

const authRoutes = require('./routes/auth');

app.use('/api/auth', authRoutes);

9. Create middleware to verify JWT:

middleware/verifyToken.js:

const jwt = require('jsonwebtoken');

module.exports = function(req, res, next) {
    const token = req.header('auth-token');
    if (!token) return res.status(401).send('Access denied');

    try {
        const verified = jwt.verify(token, process.env.JWT_SECRET);
        req.user = verified;
        next();
    } catch (err) {
        res.status(400).send('Invalid token');
    }
};

10. Create a protected route:

routes/posts.js:

const express = require('express');
const verifyToken = require('../middleware/verifyToken');

const router = express.Router();

router.get('/', verifyToken, (req, res) => {
    res.send('This is a protected post');
});

module.exports = router;

Add this route to server.js:

const postRoutes = require('./routes/posts');

app.use('/api/posts', postRoutes);

Frontend Setup:

1. Create a new React app:

npx create-react-app client
cd client
npm install axios

2. Create a Login and Register component:

Login.js:

import React, { useState } from 'react';
import axios from 'axios';

function Login() {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleSubmit = async (e) => {
        e.preventDefault();
        const response = await axios.post('/api/auth/login', { username, password });
        localStorage.setItem('token', response.data);
    };

    return (
        <div>
            <h2>Login</h2>
            <form onSubmit={handleSubmit}>
                <input type="text" placeholder="Username" onChange={(e) => setUsername(e.target.value)} />
                <input type="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} />
                <button type="submit">Login</button>
            </form>
        </div>
    );
}

export default Login;

Register.js:

import React, { useState } from 'react';
import axios from 'axios';

function Register() {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleSubmit = async (e) => {
        e.preventDefault();
        await axios.post('/api/auth/register', { username, password });
    };

    return (
        <div>
            <h2>Register</h2>
            <form onSubmit={handleSubmit}>
                <input type="text" placeholder="Username" onChange={(e) => setUsername(e.target.value)} />
                <input type="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} />
                <button type="submit">Register</button>
            </form>
        </div>
    );
}

export default Register;

3. Create a component to display the protected resource:

Posts.js:

import React, { useEffect, useState } from 'react';
import axios from 'axios';

function Posts() {
    const [posts, setPosts] = useState('');

    useEffect(() => {
        const fetchPosts = async () => {
            const token = localStorage.getItem('token');
            const response = await axios.get('/api/posts', {
                headers: { 'auth-token': token }
            });
            setPosts(response.data);
        };

        fetchPosts();
    }, []);

    return (
        <div>
            <h2>Protected Posts</h2>
            {posts}
        </div>
    );
}

export default Posts;

4. Add components to App.js and setup

routing:

Install react-router-dom:

npm install react-router-dom

App.js:

import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Login from './Login';
import Register from './Register';
import Posts from './Posts';

function App() {
    return (
        <Router>
            <Switch>
                <Route path="/login" component={Login} />
                <Route path="/register" component={Register} />
                <Route path="/posts" component={Posts} />
            </Switch>
        </Router>
    );
}

export default App;

5. Setup proxy for the client:

In client/package.json, add:

"proxy": "http://localhost:5000"

Running the Application:

  1. Start the backend:

    node server.js
  2. Start the frontend:

    cd client
    npm start

This is a basic MERN stack application with JWT authentication. You can register a user, log in, and access a protected resource. Remember to handle errors and edge cases in a real-world application.


Enhancements and Best Practices:

Backend:

  1. Error Handling: Implement a global error handler using middleware to catch and handle all errors.
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});
  1. Rate Limiting: Use packages like express-rate-limit to prevent brute-force attacks.

  2. Logging: Use morgan or winston for logging requests and errors.

  3. Validation: Use express-validator or joi to validate user input.

Frontend:

  1. State Management: Consider using Redux or Context API for state management, especially when your app grows.

  2. Protected Routes: Use a higher-order component or a custom route to protect frontend routes.

import { Route, Redirect } from 'react-router-dom';

function ProtectedRoute({ component: Component, ...rest }) {
    const token = localStorage.getItem('token');
    return (
        <Route
            {...rest}
            render={props =>
                token ? <Component {...props} /> : <Redirect to="/login" />
            }
        />
    );
}

Usage:

<ProtectedRoute path="/posts" component={Posts} />
  1. Error Handling: Handle API errors gracefully. Show user-friendly error messages.

  2. Loading States: Show a spinner or a loading message while fetching data.

  3. Logout: Implement a logout feature that clears the JWT token from local storage.

  4. Token Expiry: Handle JWT token expiry. If a token is expired, prompt the user to log in again.

  5. HTTPS: Always use HTTPS in production to ensure the JWT token is transmitted securely.

  6. Refresh Tokens: Implement refresh tokens to get a new access token without asking the user to log in again.

  7. Styling: Use libraries like styled-components or frameworks like Bootstrap or Material-UI to improve the UI.

  8. Testing: Write unit and integration tests using libraries like Jest and React Testing Library.

Conclusion:

This guide provides a basic understanding of setting up JWT authentication in a MERN stack application. However, when building a real-world application, always consider security best practices, handle edge cases, and test thoroughly.

Originally posted by @akash-coded in https://github.com/akash-coded/mern/discussions/199

05-yuvraj-singh commented 1 year ago

hey , do assign me this issue if no one's working on it ...