Node.js GitHub Authentication using Passport.js and MongoDB
Tags: javascript, mongodb, node, github
Published: 2021-11-08 14:38:54

In this article, you will learn how to authenticate with GitHub using Passport.js in a Nodejs express app. 

You can get the source code for this project [here](https://github.com/Joshuajee/passport-github-auth), this project can be used as a boilerplate code when setting up an express app that uses Passportjs for authentication.

## Prerequisites:
- Basic Knowledge of NodeJS
- Node JS should be installed on your system.
## What is Passport.js?

Passport is authentication middleware for Node.js. It is very flexible and modular. A comprehensive set of strategies supports authentication using a username and password, Google, Facebook, Apple, Twitter, and more. Find out more about Passport [here](http://www.passportjs.org/).

## Creating a Github Application

Before using passport-github2, you must have a Github account and register an application with Github. If you have not done this, You can do that [here](https://github.com/settings/applications/new).
Your Homepage URI and Callback URI should match the one in your application. Your application will be issued a Client ID and Client secret, which this strategy needs to work.

![Github Application setup page](https://paper-attachments.dropbox.com/s_96D7FF9291F95727D81A56AA1A763CC4A64AC3AFC24A94BFB0A28786BA9BD4FB_1635810132201_git.png)

## Setting up our Project

To start, create a Nodejs project by running this command.


    npm init

Install the following packages by running these commands.


    npm install express express-session ejs mongoose passport passport-github2 dotenv nodemon

If the command is successful, you will see something like the image below, `node_modules` folder will be created and `package-lock.json` file will also be created.

![](https://paper-attachments.dropbox.com/s_96D7FF9291F95727D81A56AA1A763CC4A64AC3AFC24A94BFB0A28786BA9BD4FB_1635801691464_pack.png)


Below is the project structure for this project.
📦passportjs
 ┣ 📂controller
 ┃ ┣ 📜account.js
 ┃ ┗ 📜auth.js
 ┣ 📂model
 ┃ ┗ 📜UserModel.js
 ┣ 📂routes
 ┃ ┣ 📜account.js
 ┃ ┗ 📜auth.js
 ┣ 📂utils
 ┃ ┗ 📜github.js
 ┣ 📂views
 ┃ ┣ 📜account.ejs
 ┃ ┗ 📜index.ejs
 ┣ 📜.env
 ┣ 📜.gitignore
 ┣ 📜app.js
 ┣ 📜package-lock.json
 ┣ 📜package.json
 ┣ 📜README.md
 ┗ 📜server.js

## Setting up our Express Server

At this point, our application set. Now let’s go ahead and set up our express server. To get started, first create `server*.js` file* in the project root directory. 
Next, import the mongoose for our database connection and `dotenv` to lead our [environment variables](https://www.twilio.com/blog/2017/08/working-with-environment-variables-in-node-js.html) with the code below:


    const mongoose = require('mongoose');
    const dotenv = require('dotenv');

Next, create a `.env` file in your project root directory, where we will store our environment variables later in this session. Then make the available in our application with the code below.


    dotenv.config({ path: './.env' });

Import app.js into the code in this file export an express app, this app will be explained next in this article.


    const app = require('./app');

Next, we make a connection to the mongoose database with the code below.


    mongoose
      .connect(process.env.DATABASE, { useUnifiedTopology: true })
      .then(() => console.log('DB connection successful!'));

Next, we assign a port to the express application. The application will be listening to the port provided by the environment or port 8081 if there is no environment port.


    const port = process.env.PORT || 8081;
    
    app.listen(port, () => {
      console.log(`App running on port ${port}...`);
    });

The following line of codes listens for the following events `uncaughtException`, `unhandledRejection`, and `SIGTERM` respectively, and shut down the server once either one of them occurs.


    process.on('uncaughtException', err => {
        console.log('UNCAUGHT EXCEPTION! 💥 Shutting down...');
        console.log(err.name, err.message);
        process.exit(1);
    });
    
    process.on('unhandledRejection', err => {
        console.log('UNHANDLED REJECTION! 💥 Shutting down...');
        console.log(err.name, err.message);
        server.close(() => {
          process.exit(1);
        });
    });
    
    process.on('SIGTERM', () => {
        console.log('👋 SIGTERM RECEIVED. Shutting down gracefully');
        server.close(() => {
          console.log('💥 Process terminated!');
        });
    });

Your `.env` file should look like this. Put your credentials on the required fields. 


    DATABASE = your-mongo-db-uri
    GITHUB_CLIENT_ID = your-github-app-client-id
    GITHUB_CLIENT_SECRET = your-github-app-client-secret
    GITHUB_CALLBACK_URL = your-github-app-callback-url
    SESSION_SECRET = your-app-session-secret-it-can-be-any-string-of-your-choice
## Setting up our Express Application

Now let’s go ahead and setup our express application. To get started, create an `app.js` in the project root directory.  first we import `express`, next we import `express-session` this is a middleware for handling user sessions in express.
Then import two route handlers on for handling authentication request and the other for handling request in user account. These route handlers will be explained next.


    const express = require('express');
    const session = require('express-session');
    const authRouter = require('./routes/auth');
    const accountRouter = require('./routes/account');

Here is where we create the express and by calling the express function which is a top level function exported by the express module and assign it to the `app` variable.


    const app = express();

Next, we configure the directory where the template files will be located. The first line of code set the view directory to `/views`. The second line set the view engine to ejs. Learn more about ejs [here](https://ejs.co/). 


    app.set('views', __dirname + '/views');
    app.set('view engine', 'ejs');

Next we use the `express-session` middleware so that we can support persistent login from users. the session(options) receives an object of settings read the express-session documentation to learn [more](https://expressjs.com/en/resources/middleware/session.html).


> The default server-side session storage, MemoryStore, is not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing. For a list of stores, see [compatible session stores](https://expressjs.com/en/resources/middleware/session.html#compatible-session-stores).


    app.use(
      session(
        { 
          secret: process.env.SESSION_SECRET, 
          resave: false, 
          saveUninitialized: false 
        }));

Here we redirect the user to the `localhost:8081/auth` route once they visit `localhost:8081/`


    app.get('/', function(req, res){
      res.redirect('/auth');
    });

Next, we configure two routers on the app for handling `localhost:8081/auth/*` requests and the other for handling account request `localhost:8081/account/*` these routers will be discussed next.


    // set Routes
    app.use('/auth', authRouter);
    app.use('/account', accountRouter);

export the express app


    module.exports = app;
## Creating our Application Routers

First, we create a route directory. The files in this directory will be used as route handlers to handle different routes in our application.

**Create Authentication Router**
Create `auth.js` file inside the `route` directory, then import `express`  and `passport`.


    const express = require('express');
    const passport = require('passport');

We import `github` which is an authentication middleware based on passport GitHub strategy this middleware will be explained later in this article. Also, import `authController`. This module is meant to contain a bunch of functions that control user authentication but for now, it just contains the logout function.


    const github = require('./../utils/github');
    const authController = require('./../controller/auth');

We configure passport to use the `github` middleware.


    passport.use(github);

Here we use the `express.Router()` class to create modular, mountable route handlers. then we use the `passport.initialize()` function in the router this function is needed to initialize `passportjs` on our routes, `passport.session()` function enables persistent login with `passportjs` in our route it handles session.


    const router = express.Router();
    
    router.use(passport.initialize());
    router.use(passport.session());

`serializeUser` determines which data of the user object should be stored in the session. The result of the `serializeUser` function is attached to the session as `req.session.passport.user = {}`. Here we store the whole user object

The first argument of `deserializeUser` corresponds to the user object that was given to the `done` function.  The object is attached to the request object as `req.user`


    passport.serializeUser(function(user, done) {
        done(null, user);
    });
    
    passport.deserializeUser(function(obj, done) {
        done(null, obj);
    });

This line of code renders the `index.ejs` file in the view directory once the user visits the `localhost:8081/auth` route.


    router.get('/', function(req, res){
        res.render('index', { user: req.user });
    });

This line of codes tries to authenticate the user with GitHub once the 
`localhost:8081/auth/github` route is visited. It Redirect the user to a GitHub conscent page and request for the user authorization, once the user authorize the app, it redirects the user back to the callback url which is `localhost:8081/auth/github/callback` for this application on successful login the user will be redirected to `localhost:8081/account`  by this line of code `res.redirect('/account'));` . 


    router.get('/github', passport.authenticate('github', { scope: [ 'user:email' ] }));
    
    router.get('/github/callback', 
        passport.authenticate('github', { failureRedirect: '/' }),
        (req, res) =>  res.redirect('/account'));

Once the user visit `localhost:8081/auth/logout`. the session will be destroyed and the user will have to login again.


    router.get('/logout', authController.logout);
    
    module.exports = router;

**Create account routes**
Create `account.js` file inside the `route` directory, the following codes below do the same function as the ones on `auth.js` , `accountController.js` contains functions for handling user accounts.


    const express = require('express');
    const passport = require('passport');
    const accountController = require('./../controller/account');
    
    const router = express.Router();
    
    router.use(passport.initialize());
    router.use(passport.session());
    
    passport.serializeUser(function(user, done) {
        done(null, user);
    });
    
    passport.deserializeUser(function(obj, done) {
        done(null, obj);
    });

This route handler, handles get requests sent to this route `localhost:8081/account`.

    
    router.get('/', accountController.user);
    module.exports = router;


## Creating Utility 

First, we create a `utils` directory. This directory is going to contain all our utility functions, for this project.

    

**Create github middleware**
This code exports a middleware this middleware is required when making an authentication request with `passport-github2` strategy. Here we use passport-github2 strategy, we pass the configuration object which includes the `ClientId`, `ClientSecret`, and `CallbackUrl`, these values should match the one used in creating the github application. if these values are correct and up to date the callback function with four parameters with be called

- accessToken - GitHub access Token
- refreshToken - GitHub refresh Token
- profile - contains user data gotten from GitHub
- done - this is callback function with two arguments error and data is called, the profile.id data is used to query the mongo database to check if the user account exists, if it doesn't exist the user is created with the data gotten from github.

`User.findOne({githubId: profile.id })` checks if a user with the same `github` profile exists in the database, if it does exist the `return done(null, data);` function will be called with the user data. If no user exists the user will be created and the `return done(null, data);` will be called with the user data.


    const GitHubStrategy = require('passport-github2').Strategy;
    const User = require('../model/UserModel');
    
    module.exports = new GitHubStrategy({
        clientID: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        callbackURL: process.env.GITHUB_CALLBACK_URL
      },
      function(accessToken, refreshToken, profile, done) {
    
        User.findOne({githubId: profile.id }).then((data, err) => {
    
          if (!data) return User.create({
            githubId: profile.id,
            fullname: profile.displayName,
            username: profile.username,
            location: profile._json.location,
            phone: profile._json.phone,
            email: profile._json.email,
            profilePhoto: profile._json.avatar_url
          }).then((data, err) => {
            return done(null, data);
          });
    
          else return done(null, data);
        });
      }
    );
## Creating Database Model

Create a `model` directory. This directory is going to contain all our database Models, for this project.

    

**Create User Model**
First, we create a `userModel.js` file inside the `model` directory, import `mongoose` into the project, then create a user schema.

Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection.

Models are fancy constructors compiled from `Schema` definitions. An instance of a model is called a document. Models are responsible for creating and reading documents from the underlying MongoDB database.


    const mongoose = require('mongoose');
    
    const userSchema = new mongoose.Schema(
      {
        fullname: { type: String },
        username: { type: String },
        githubId: { type: String, unique: true },
        location: { type: String },
        phone: { type: String },
        email: { type: String, lowercase: true },
        profilePhoto: { type: String, default: '' }
      },
      { timestamps: true }
    );
    
    const User = mongoose.model('User', userSchema);
    
    module.exports = User;
## Creating Controllers

Create a `controller` directory. This directory is going to contain all our controllers for this project.

**Authentication controller**
The `auth.js` controller contains one function `logout` to destroy user session and redirect user to the homepage.

    
    exports.logout = (req, res, next) => {
        req.logout();
        res.redirect('/');
    };

**Authentication controller**
The `account.js` controller contains one function `user`, `req.user` get the user data from the request parameter, `if (!user) res.redirect('/');` redirect the user to `localhost:8081/` if the user exist it readers the `account.ejs` templete.

    
    exports.user = (req, res, next) => {
        const user = req.user;
    
        if (!user) res.redirect('/');
    
        res.render('account', {user: user});
    };


## Creating Views

Create a `views` directory, this directory will hold all the ejs templating codes for the application.

**Create the Index ejs template**
Create a file `index.ejs` inside the `views` directory. This templete renders a link to authenticate with github when user session is not available `<h2>Welcome! <a href="/auth/github">Login with GitHub</a> </h2>` and renders a link to view user account, when user session is available `<h2>Hello, <%= user.fullname %> <a href="/account">View Account</a></h2>`.


    <% if (!user) { %>
        <h2>Welcome! <a href="/auth/github">Login with GitHub</a> </h2>
    <% } else { %>
        <h2>Hello, <%= user.fullname %> <a href="/account">View Account</a></h2>
    <% } %>

**Create the Account ejs template**
Create a file `account.ejs` inside the `views` directory. This template simply displays user information, stored in the database.


    <div>
        <p>Full Name: <%= user.fullname %></p>
        <p>Username: <%= user.username %></p>
        <p>Email: <%= user.email %></p>
        <p>location: <%= user.location %></p>
        <p><a href="/auth/logout">Logout </a></p>
        <img src=<%= user.profilePhoto %> />
    </div>

## Conclusion

In this article you have learned how to authenticate users, using `passport-github2` strategy, You learned how to create and configure a github application, and maintain user sessions within your application while using ejs as templating engine.