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:
- Create a new cluster on MongoDB Atlas.
- Whitelist your IP and get the connection string.
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:
- Start the backend:
- Start the frontend:
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:
- 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!');
});
-
Rate Limiting: Use packages like express-rate-limit to prevent brute-force attacks.
-
Logging: Use morgan or winston for logging requests and errors.
-
Validation: Use express-validator or joi to validate user input.
Frontend:
-
State Management: Consider using Redux or Context API for state management, especially when your app grows.
-
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} />
-
Error Handling: Handle API errors gracefully. Show user-friendly error messages.
-
Loading States: Show a spinner or a loading message while fetching data.
-
Logout: Implement a logout feature that clears the JWT token from local storage.
-
Token Expiry: Handle JWT token expiry. If a token is expired, prompt the user to log in again.
-
HTTPS: Always use HTTPS in production to ensure the JWT token is transmitted securely.
-
Refresh Tokens: Implement refresh tokens to get a new access token without asking the user to log in again.
-
Styling: Use libraries like styled-components or frameworks like Bootstrap or Material-UI to improve the UI.
-
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 #199
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 -y2. Install necessary packages:
3. Setup MongoDB:
4. Create a
.envfile for environment variables:5. Setup Express and MongoDB:
server.js:6. Create User model:
models/User.js:7. Create routes for registration and login:
routes/auth.js:8. Add routes to
server.js:9. Create middleware to verify JWT:
middleware/verifyToken.js:10. Create a protected route:
routes/posts.js:Add this route to
server.js:Frontend Setup:
1. Create a new React app:
npx create-react-app client cd client npm install axios2. Create a Login and Register component:
Login.js:Register.js:3. Create a component to display the protected resource:
Posts.js:4. Add components to
App.jsand setuprouting:
Install react-router-dom:
App.js:5. Setup proxy for the client:
In
client/package.json, add:Running the Application:
cd client npm startThis 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:
Rate Limiting: Use packages like
express-rate-limitto prevent brute-force attacks.Logging: Use
morganorwinstonfor logging requests and errors.Validation: Use
express-validatororjoito validate user input.Frontend:
State Management: Consider using
ReduxorContext APIfor state management, especially when your app grows.Protected Routes: Use a higher-order component or a custom route to protect frontend routes.
Usage:
Error Handling: Handle API errors gracefully. Show user-friendly error messages.
Loading States: Show a spinner or a loading message while fetching data.
Logout: Implement a logout feature that clears the JWT token from local storage.
Token Expiry: Handle JWT token expiry. If a token is expired, prompt the user to log in again.
HTTPS: Always use HTTPS in production to ensure the JWT token is transmitted securely.
Refresh Tokens: Implement refresh tokens to get a new access token without asking the user to log in again.
Styling: Use libraries like
styled-componentsor frameworks likeBootstraporMaterial-UIto improve the UI.Testing: Write unit and integration tests using libraries like
JestandReact 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 #199