Unlocking the Power of JWT Authentication in MERN Apps
The Final Piece of the Puzzle
To bring everything together and create a seamless user experience, we’ll create a new endpoint for users to sign in, validate users on the server-side, create an auth service, handle errors and success, generate and send JWT from the server, update client-side logic, fix bugs, and deploy the complete code.
Creating a User-Friendly Endpoint
We’ll edit our server.js
file and add a new route for users to sign in. We’ll also create a sample store for users, where we’ll store key-value pairs for each user.
const express = require('express');
const app = express();
app.post('/signin', (req, res) => {
// Sign in logic
});
app.listen(3000, () => {
console.log('Server started on port 3000');
});
Validating Users on the Server-Side
We’ll check if the user is present in our system and validate their password. If the user isn’t found or the credentials are incorrect, we’ll send an error message with a status code of 403 Forbidden.
app.post('/signin', (req, res) => {
const { username, password } = req.body;
const user = getUserByUsername(username);
if (!user ||!validatePassword(user, password)) {
return res.status(403).send({ error: 'Invalid credentials' });
}
// Sign in logic
});
Creating an Auth Service
We’ll create an auth service that will talk to the Users/SignIn API endpoint. We’ll define a function that takes in a username, password, and callback function as parameters.
class AuthService {
signIn(username, password, callback) {
fetch('/signin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
.then(response => response.json())
.then(data => callback(null, data))
.catch(error => callback(error));
}
}
Handling Errors and Success
We’ll handle errors by sending a second parameter to the callback function, passing the error object as the first one. If the error parameter is true, we’ll set an error state with the received message and stop proceeding. If not, we’ll check the status code to ensure it’s 200 OK.
AuthService.signIn('username', 'password', (error, data) => {
if (error) {
return setError(error.message);
}
if (data.statusCode!== 200) {
return setError('Invalid response');
}
// Sign in success
});
Generating and Sending JWT from the Server
We’ll update the sign-in API response to send a success message along with a JWT generated on the server. We’ll create a JWT based on our default headers and claims, and send it to the client.
const jwt = require('jsonwebtoken');
app.post('/signin', (req, res) => {
// Sign in logic
const token = jwt.sign({ username }, 'ecretKey', { expiresIn: '1h' });
res.send({ message: 'Sign in successful', token });
});
Updating Client-Side Logic
We’ll update the client-side logic to handle the JWT response. We’ll decode the JWT and store it in the state, and also persist the login after refresh by storing the JWT in localStorage.
const token = authService.signIn('username', 'password');
localStorage.setItem('token', token);
const decodedToken = jwtDecode(token);
setState({ username: decodedToken.username });
Bug Fixes and Comments
We’ll fix some mistakes we’ve missed during development, such as clearing error messages during successful events and after signing out.
AuthService.signOut(() => {
setError(null);
localStorage.removeItem('token');
});
Deploying the Complete Code
Finally, we’ll deploy our complete code using React’s production build and Heroku.
$ npm run build
$ git push heroku master
Check out the complete code on GitHub.