In Part 2 of this tutorial series, we continued working on our Express server. We did the following:
- Added the rest of the endpoints that will be needed for our server
- Created a user mongoose model for our database
- Created the connection for connecting to MongoDB
- Started updating the routes to send and retrieve data from MongoDB
In Part 3 of this tutorial series, we will continue working on our server by updating the rest of the endpoints for sending data to MongoDB, and we will add authentication to our endpoints.
You can download all of the files associated with the source code for Part 3 here.
If you didn’t complete Part 2 and would like to continue from there, you can find the code for it here.
Let’s get started!
Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.
Updating Score Routes
Currently, the /scores and /submit-score endpoints just have placeholder logic that will return a 200 response code. We will now update the /scores endpoint to get the top ten high scores from the database, and update the /submit-score endpoint to update a single user’s score. To do this, open routes/secure.js and replace all of the code in the file with the following:
const express = require('express');
const asyncMiddleware = require('../middleware/asyncMiddleware');
const UserModel = require('../models/userModel');
const router = express.Router();
router.post('/submit-score', asyncMiddleware(async (req, res, next) => {
const { email, score } = req.body;
await UserModel.updateOne({ email }, { highScore: score });
res.status(200).json({ status: 'ok' });
}));
router.get('/scores', asyncMiddleware(async (req, res, next) => {
const users = await UserModel.find({}, 'name highScore -_id').sort({ highScore: -1}).limit(10);
res.status(200).json(users);
}));
module.exports = router;
Let’s review the code we just added:
- First, we imported
asyncMiddlewareandUserModel, and then we wrapped both of the routes in theasyncMiddlewaremiddleware we created in part two. - Next, in the
submit-scoreroute, we grabbed theemailandscorevalues from the request body. We then used theupdateOnemethod on theUserModelto update a single document in the database where the providedemailvalue matches theemailproperty on the record. - Then, in the
/scoresroute, we used thefindmethod on theUserModelto search for documents in the database.- The
findmethod takes two arguments, the first is an object that is used for limiting the documents that are returned from the database. By leaving this as an empty object, all documents will be returned from the database. - The second argument is a string that allows us to control which fields we want to be returned on the results that are returned to us. This argument is optional, and if it is not provided then all fields will be returned. By default, the
_idfield will always be returned, so to exclude it we need to use-_idin this argument.
- The
- We then called the
sortmethod to sort the results that are returned. This method allows you to specify the field you would like to sort by, and by setting that value to-1the results will be sorted in descending order. - Finally, we called the
limitmethod to make sure we return 10 documents at max.
Now, if you save your code changes and start the server, you will be able to test the updated routes. To test the /submit-score endpoint, you will need to use curl, Postman, or some other method for sending a POST request. For the rest of this tutorial, we will be using curl.
In the terminal, open a new tab or window and enter the following code:
curl -X POST \
http://localhost:3000/submit-score \
-H 'Content-Type: application/json' \
-d '{
"email": "test4@test.com",
"score": "100"
}'Once you submit the request, you should get the status ok message. To test the /scores endpoint, open a new tab in your browser and visit the following URL: http://localhost:3000/scores. You should see an array of objects that include the name and highscore fields.
Authentication
Now that the score endpoints are connected to our database, we will now start working on adding authentication to our server. For the authentication, we will be using passport.js along with passport-jwt. Passport is an authentication middleware for Nodejs that can easily be used with Express, and it supports many different types of authentication.
Along with passport, we will be using JSON web tokens for validating users. In addition to using a JWT for authenticating users, we will also be using a refresh token to allow a user to update their main JWT. The reason we are doing this is that we want the main JWT to be short-lived, and instead of requiring the player to have to keep re-logging in to get a new token, they can instead use their refresh token to update the main JWT, since it will be long-lived.
To get started, the first thing we need to do is install the required packages. In the terminal, run the following command:
npm install --save cookie-parser passport passport-local passport-jwt jsonwebtoken
This will install passport along with the two strategies we will need for our server. passport-local allows us to authenticate with a username and password on our server. passport-jwt allows us to authenticate with a JSON web token. Lastly, cookie-parser allows us to parse the cookie header and populate,req.cookies which is where we will be placing the JWT that is generated.
To get started, create a new folder in the root folder called auth and inside this file create a new file called auth.js. In auth.js, add the following code:
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const JWTstrategy = require('passport-jwt').Strategy;
const UserModel = require('../models/userModel');
// handle user registration
passport.use('signup', new localStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, async (req, email, password, done) => {
try {
const { name } = req.body;
const user = await UserModel.create({ email, password, name});
return done(null, user);
} catch (error) {
done(error);
}
}));In the code above, we did the following:
- First, we imported the
passport,passport-local, andpassport-jwt. Then, we imported theuserModel. - Next, we configured
passportto use a new local strategy when thesignuproute is called. ThelocalStrategytakes two arguments: anoptionsobject and a callback function.- For the
optionsobject, we set theusernameFieldandpasswordFieldfields. By default, if these fields are not providedpassport-localwill expect theusernameandpasswordfields, and if we want to use a different combination we need to provide them. - Also, in the
optionsobject, we set thepassReqToCallbackfield and we set it totrue. By setting this field totrue, the request object will be passed to the callback function.
- For the
- Then, in the callback function, we took the logic for creating the user that was in the
signuproute and we placed it here. - Lastly, we called the
donefunction that was passed as an argument to the callback function.
Before we update the server and routes to use the new authentication, we will need to finish configuring passport. In auth.js, add the following code at the bottom of the file:
// handle user login
passport.use('login', new localStrategy({
usernameField: 'email',
passwordField: 'password'
}, async (email, password, done) => {
try {
const user = await UserModel.findOne({ email });
if (!user) {
return done(null, false, { message: 'User not found' });
}
const validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, { message: 'Wrong Password' });
}
return done(null, user, { message: 'Logged in Successfully' });
} catch (error) {
return done(error);
}
}));
// verify token is valid
passport.use(new JWTstrategy({
secretOrKey: 'top_secret',
jwtFromRequest: function (req) {
let token = null;
if (req && req.cookies) token = req.cookies['jwt'];
return token;
}
}, async (token, done) => {
try {
return done(null, token.user);
} catch (error) {
done(error);
}
}));Let’s review the code we just added:
- We set up another local strategy for the
loginroute. Then in the callback function, we took the logic for logging a user in from theloginroute and we placed it here. - Next, we configured passport to use a new JWT strategy. For the JWT strategy, we provided two arguments: an options object and a callback function.
- In the options object, we provided two fields:
secretOrKeyandjwtFromRequest. secretOrKeyis used for signing the JWT that is created. For this tutorial, we used a placeholder secret and normally you would want to pull this from your environment variables or use some other secure method, and you would want to use a much more secure secret.jwtFromRequestis a function that is used for getting thejwtfrom the request object. For this tutorial, we will be placing thejwtin a cookie, so in the function, we pull thejwttoken from the request object cookie if it exists otherwise we returnnull.- Lastly, in the callback function, we call the
donefunction that was provided to the callback.
- In the options object, we provided two fields:
Now that we have finished configuring passport, we need to update app.js to use passport. To do this, open app.js and add the following code at the top of the file with the other require statements:
const cookieParser = require('cookie-parser');
const passport = require('passport');Then, add the following code below the `app.use(bodyParser.json());` line:
app.use(cookieParser());
// require passport auth
require('./auth/auth');Finally, replace this line: `app.use(‘/’, secureRoutes);` with the following code:
app.use('/', passport.authenticate('jwt', { session : false }), secureRoutes);In the code above, we did the following:
- First, we imported
cookie-parserandpassport. - Then, told our express app to use
cookie-parser. By usingcookie-parser, the request object will have the cookies included. - Lastly, we imported our auth file and then updated our secure routes to use the passport JWT strategy we set up.
The last thing we need to do before we can test our code changes is, update the routes in routes/main.js. To do this, open routes/main.js and replace all of the code in the file with the following code:
const passport = require('passport');
const express = require('express');
const jwt = require('jsonwebtoken');
const tokenList = {};
const router = express.Router();
router.get('/status', (req, res, next) => {
res.status(200).json({ status: 'ok' });
});
router.post('/signup', passport.authenticate('signup', { session: false }), async (req, res, next) => {
res.status(200).json({ message: 'signup successful' });
});
router.post('/login', async (req, res, next) => {
passport.authenticate('login', async (err, user, info) => {
try {
if (err || !user) {
const error = new Error('An Error occured');
return next(error);
}
req.login(user, { session: false }, async (error) => {
if (error) return next(error);
const body = {
_id: user._id,
email: user.email
};
const token = jwt.sign({ user: body }, 'top_secret', { expiresIn: 300 });
const refreshToken = jwt.sign({ user: body }, 'top_secret_refresh', { expiresIn: 86400 });
// store tokens in cookie
res.cookie('jwt', token);
res.cookie('refreshJwt', refreshToken);
// store tokens in memory
tokenList[refreshToken] = {
token,
refreshToken,
email: user.email,
_id: user._id
};
//Send back the token to the user
return res.status(200).json({ token, refreshToken });
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
router.post('/token', (req, res) => {
const { email, refreshToken } = req.body;
if ((refreshToken in tokenList) && (tokenList[refreshToken].email === email)) {
const body = { email, _id: tokenList[refreshToken]._id };
const token = jwt.sign({ user: body }, 'top_secret', { expiresIn: 300 });
// update jwt
res.cookie('jwt', token);
tokenList[refreshToken].token = token;
res.status(200).json({ token });
} else {
res.status(401).json({ message: 'Unauthorized' });
}
});
router.post('/logout', (req, res) => {
if (req.cookies) {
const refreshToken = req.cookies['refreshJwt'];
if (refreshToken in tokenList) delete tokenList[refreshToken]
res.clearCookie('refreshJwt');
res.clearCookie('jwt');
}
res.status(200).json({ message: 'logged out' });
});
module.exports = router;
Let’s review the code we just added:
- First, we imported
jsonwebtokenandpassport, and then we removed `userModel` and `asyncMiddleware`. - Next, in the
signuproute, we added thepassport.authenticatemiddleware and set it to use the passportsignupconfiguration we created. Since we moved all of the logic for creating the use to theauth.jsfile, the only thing we need to do in the callback function is to return a 200 response. - Then, in the
loginroute we added thepassport.authenticatemiddleware and set it to use the passportloginconfiguration we created.- In the callback function, we first check to see if there was an error or if a user object was not returned from the passport middleware. If this check is true, then we create a new error and pass it to the next middleware.
- If that check is false, we then call the
loginmethod that is exposed on the request object. This method is added by passport automatically. When we call this method, we pass theuserobject, an options object, and a callback function as arguments. - In the callback function, we create two JSON web tokens by using the `jsonwebtoken` library. For the JWTs, we include the id and email of the user in the JWT payload, and we set the main
tokento expire in five minutes and therefreshTokento expire in one day. - Then, we stored both of these tokens in the response object by calling the
cookiemethod, and we stored these tokens in memory so we can reference them later in the token refresh endpoint. Note: for this tutorial, we are storing these tokens in memory, but in practice, you would want to store this data in some type of persistent storage. - Lastly, we responded with a 200 response code and in the response, we send the
tokenandrefreshToken.
- Next, in the
tokenroute, we pulled theemailandrefreshTokenfrom the request body. We then checked to see if therefreshTokenis in thetokenListobject we are using for tracking the user’s tokens, and we made sure the providedemailmatches the one stored in memory.- If these do not match, or if the token is not in memory, then we respond with a 401 status code.
- If they do match, then we create a new token and store it in memory and update the response cookie with the new token.
- We then respond with a 200 response code and in the response, we send the new
token.
- Finally, in the
logoutroute, we check to request object has any cookies.- If the request object does have any cookies, then we pull the `refreshJwt` from the cookie and delete it from our in-memory token list if it exists.
- We then clear the
jwtandrefreshJwtcookies by calling theclearCookiemethod on the response object. - Lastly, we respond with a 200 response code.
Now that the routes have been updated, you can test the new authentication. To do this, save your code changes and restart your server. If you try sending a request to the `submit-score` and scores endpoints, you should a 401 unauthorized message.
If you send a login request, you should get a response that includes the token and refreshToken.
Conclusion
Now that we have finished setting up the user authentication, that brings Part 3 of this tutorial to an end. In Part 4, we will start working on our game by adding a login page, which will then take the player to the Phaser game after they log in.
I hope you enjoyed this tutorial and found it helpful. If you have any questions, or suggestions on what we should cover next, please let us know in the comments below.









