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.

Leave a Reply