MERN-Stack Infrastructure - Part 7

Learning Objectives
Students Will Be Able To: |
---|
Send the JWT to the Server in AJAX Requests |
Validate the JWT and Add the Payload to req.user |
Protect Server-Side Routes that Require A Logged In User |
Save MERN-Stack Infrastructure To a New GitHub Repo |
Create a new MERN-Stack Project from the mern-infrastructure Repo |
Road Map
- The Plan - Part 7
- Infrastructure - Part 7 of 7 (Yay!)
Videos
The Plan - Part 7
In Part 7 we will will wrap up the basic infrastructure for a MERN-Stack app.
Part 7 - Implementing Token-Based Auth (continued):
- Send the token with AJAX requests
- Check the token on the server and add a
user
property toreq
- Implement middleware to protect server-side routes
- Save MERN-Stack infrastructure to a new GH repo
- Using
mern-infrastructure
to Create MERN-Stack Projects in the Future
1. Send the Token with AJAX Requests
In order to perform user-centric CRUD, the server of course, needs to know who the user is when they make a request.
During the discussion on token-based authentication, we learned that a token, or in our case more specifically a JWT, is used to identify the user.
So how do we include the JWT when sending a request that involves user-centric functionality?
The best practice is to send the token in a header of the request named Authorization
.
What Feature Are We Going to Implement?
We could start implementing a user-centric feature of SEI CAFE, however, that would be more work than necessary, after all, we just want to implement the infrastructure of a MERN-Stack app for now.
Instead, we'll simply mock up some functionality...
AAU, I want to click a button to check the expiration of my log in.
❓ When implementing new features, where do we start?
With the UI.
Add a <button>
to <OrderHistoryPage>
We'll add our feature to the <OrderHistoryPage>
.
💪 Practice Exercise - Add the <button>
& onClick
Handler (4 minutes)
- Add a
<button>
with the content of "Check When My Login Expires" below the current<h1>
.
Hint: You must return a single root component/node.
-
Add an
onClick
prop to the<button>
and assign to it a handler namedhandleCheckToken
. -
Stub up the
handleCheckToken
function and baby step withalert('clicked');
. -
Ensure that clicking the button pops up the alert.
-
Make
handleCheckToken
anasync
function so that we can consume promises usingawait
.

Now, let's continue with the flow leading toward sending an AJAX request that includes the JWT...
Add the checkToken
Service Function
You got this...
💪 Practice Exercise - Add the checkToken
Service Function (5 minutes)
-
Stub up and export a
checkToken
function in users-service.js. -
Move the
alert('clicked');
from thehandleCheckToken
function to thecheckToken
function just stubbed up. -
Import the
checkToken
function into OrderHistoryPage.js using one of the two syntaxes we've previously used. -
Invoke the
checkToken
function from thehandleCheckToken
function. Consume the promise thatcheckToken
will ultimately return usingawait
assigning its resolved value to a variable namedexpDate
. -
After invoking
checkToken
add aconsole.log(expDate)
. -
Verify that clicking still pops up the alert.
Add the checkToken
API Function and Call It
Because we'll be making an AJAX request, we'll want to add another checkToken
function in the users-api.js API module that can be called from checkToken
in the users-service.js service module.
However, notice how the existing signUp
and login
functions in users-api.js aren't very DRY?
Here's a really clean refactor that will DRY things up in a jiffy...
Let's create a utilities/send-request.js module that will export a function that can be used in every API module in any application!
export default async function sendRequest(url, method = "GET", payload = null) {
// Fetch accepts an options object as the 2nd argument
// used to include a data payload, set headers, etc.
const options = { method };
if (payload) {
options.headers = { "Content-Type": "application/json" };
options.body = JSON.stringify(payload);
}
const res = await fetch(url, options);
// res.ok will be false if the status code set to 4xx in the controller action
if (res.ok) return res.json();
throw new Error("Bad Request");
}
Tip: Making code more DRY usually consists of recognizing repeated code, identifying what varies between the two or more functions and define those as parameters (inputs) in a new function the existing functions can invoke.
Now for the refactor of users-api.js:
// Add the following import
import sendRequest from "./send-request";
const BASE_URL = "/api/users";
// Refactored code below
export function signUp(userData) {
return sendRequest(BASE_URL, "POST", userData);
}
export function login(credentials) {
return sendRequest(`${BASE_URL}/login`, "POST", credentials);
}
Now we're ready to code the checkToken
function in users-api.js responsible for making the AJAX request to the server:
export function checkToken() {
return sendRequest(`${BASE_URL}/check-token`);
}
Note: The
sendRequest
function always returns a promise and we are passing that promise to the caller of checkToken.
Now we want to call the API module's checkToken
from within the checkToken
function in users-service.js that we coded earlier.
❓ Looking at users-service.js, do we need import checkToken
from users-api.js?
No, becauseimport * as usersAPI from './users-api';
already imports all exports.
Let's make the call, replacing the alert('clicked')
:
export function checkToken() {
// Just so that you don't forget how to use .then
return (
usersAPI
.checkToken()
// checkToken returns a string, but let's
// make it a Date object for more flexibility
.then((dateStr) => new Date(dateStr))
);
}
Refactor sendRequest
To Send the JWT
Finally, we're going to refactor send-request.js so that if there's a valid token in local storage, include it with the AJAX request in a header:
// Add the following import
import { getToken } from './users-service';
...
export default async function sendRequest(url, method = 'GET', payload = null) {
...
if (payload) {
options.headers = { 'Content-Type': 'application/json' };
options.body = JSON.stringify(payload);
}
// Add the below code
const token = getToken();
if (token) {
// Ensure the headers object exists
options.headers = options.headers || {};
// Add token to an Authorization header
// Prefacing with 'Bearer' is recommended in the HTTP specification
options.headers.Authorization = `Bearer ${token}`;
}
...
Nice, we've got the JWT being sent to the server with AJAX requests!
2. Check the Token On the Server and Add a user
Property To req
In Unit 2, we relied heavily on the fact that our OAuth/Passport code assigned the logged in user's document to req.user
.
We want some of that goodness!
IMPORTANT: As discussed when token-based auth was introduced, the
req.user
property will contain the user's info from the JWT's payload - it will not be a MongoDB document. If you need to modify the user's document, which should be uncommon, it will have to be retrieved from the database.
Add the checkToken
Middleware to server.js
As we learned many moons ago, middleware is used to process requests in an Express app.
Yay! Another opportunity to write a custom middleware function that:
- Checks if there's a token sent in an
Authorization
header of the HTTP request. For additional flexibility, we'll also check for a token being sent as a query string parameter. - Verifies the token is valid and hasn't expired.
- Decodes the token to obtain the user data from its payload.
- Then finally, adds the user payload to the Express request object.
First, create the module for the middleware function in the config folder:
touch config/checkToken.js
Now for some fun code:
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
// Check for the token being sent in a header or as a query parameter
let token = req.get("Authorization") || req.query.token;
if (token) {
// Remove the 'Bearer ' if it was included in the token header
token = token.replace("Bearer ", "");
// Check if token is valid and not expired
jwt.verify(token, process.env.SECRET, function (err, decoded) {
// If valid token, decoded will be the token's entire payload
// If invalid token, err will be set
req.user = err ? null : decoded.user;
// If your app cares... (optional)
req.exp = err ? null : new Date(decoded.exp * 1000);
return next();
});
} else {
// No token was sent
req.user = null;
return next();
}
};
Now we need to mount the above middleware function so that it processes every request:
...
app.use(express.static(path.join(__dirname, 'build')));
// Middleware to verify token and assign user object of payload to req.user.
// Be sure to mount before routes
app.use(require('./config/checkToken'));
...
Add a Route to Test Out the Goodness
Add the following route to routes/api/users.js:
...
const usersCtrl = require('../../controllers/api/users');
// GET /api/users/check-token
router.get('/check-token', usersCtrl.checkToken);
...
Create the checkToken
Controller Function
Keep following the flow...
function checkToken(req, res) {
// req.user will always be there for you when a token is sent
console.log("req.user", req.user);
res.json(req.exp);
}
Don't forget to add checkToken
to the exported object.
❓ Where did the req.exp
property come from?
The checkToken middleware function we just mounted in server.js
That should do it!

Be sure to checkout the req.user
being logged in the Express server's terminal too:

😍
3. Implement Middleware to Protect Server-Side Routes
Any route/controller action that accesses req.user
needs to ensure that the request is coming from a logged in user.
Yup, another opportunity for a custom middleware function:
touch config/ensureLoggedIn.js
Doesn't take much code:
module.exports = function (req, res, next) {
// Status code of 401 is Unauthorized
if (!req.user) return res.status(401).json("Unauthorized");
// A okay
next();
};
Now we can use it within any router module with routes that need to ensure that there's a logged in user.
Let's require it in routes/api/users.js and use it to protect the check token functionality we just coded:
const usersCtrl = require("../../controllers/api/users");
// require the authorization middleware function
const ensureLoggedIn = require("../../config/ensureLoggedIn");
// Insert ensureLoggedIn on all routes that need protecting
router.get("/check-token", ensureLoggedIn, usersCtrl.checkToken);
Congrats - that wraps up the infrastructure code for a MERN-Stack app!
4. Save MERN-Stack Infrastructure To a New GitHub Repo
You'll definitely want to use the infrastructure we've coded over the last few days to launch your capstone project and likely future MERN-Stack projects as well.
First, let's update the README.md to something like:
# MERN-Stack Infrastructure
Clone this repo to provide the starter code for a comprehensive MERN-Stack project including token-based authentication.
Reset the Commit History
If you have not synced your code at any time during the 7 parts, you won't have any commits made by me and can thus skip this section.
So that you don't have commits made by your me, let's reset the local repo:
rm -rf .git
git init
Next, commit your code as it stands:
git add -A
git commit -m "MERN-Stack Infrastructure"
Create a GitHub Repo for mern-infrastructure
Next, go to your personal GitHub account and create a new repo named whatever you wish.
FYI, I'm going to name mine mern-infrastructure:

Now click to copy the new repo's URL:

Now let's add a remote that points to the new repo...
Add the Remote
We'll need to add a remote so that we can push to the new GH repo in the cloud.
If you reset the local repo, run:
git remote add origin <paste the copied url>
Otherwise, if you didn't reset the repo because you didn't sync, run the following to change where origin
points to:
git remote set-url origin <paste the copied url>
Now you can push the code:
git push -u origin main
Congrats - refreshing the repo should confirm that the repo is ready for cloning as needed!
5. Using mern-infrastructure
to Create MERN-Stack Projects in the Future
Here's the process to create a new MERN-Stack project that starts with the infrastructure code:
- Clone the mern-infrastructure repo:
git clone <url of mern-infrastructure> <name-of-project>
Note that the folder created will be same as
<name-of-project>
instead of mern-infrastructure
-
cd <name-of-project>
-
Install the Node modules:
npm i
-
Create a .env (
touch .env
) and add entries forDATABASE_URL
andSECRET
-
Update the
"name": "mern-infrastructure"
in package.json to the name of your project. -
Create a new repo on your personal GH account.
-
Copy the new GH repo's URL.
-
Update the remote's URL:
git remote set-url origin <paste the copied GH url>
-
Make the initial commit:
git add -A && git commit -m "Initial commit"
-
Push for the first time:
git push -u origin main
-
Have fun coding your new project and don't forget to make frequent commits!