Adding Facebook Authentication in a .NET Core API (Login & Register)

Adding Facebook Authentication in a .NET Core API (Login & Register)

Adding Facebook Authentication in a .NET Core API (Login & Register)

Adding Facebook Authentication in a .NET Core API (Login & Register)

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Build an Authentication System for Vue.js 2 using Facebook Login App

Build an Authentication System for Vue.js 2 using Facebook Login App

In this Vue.js tutorial, we will build an Authentication system for Vue.js 2 App using the Facebook Login App

In this Vue.js tutorial, we will build an authentication system for Vue.js 2 app using the Facebook login app. As usual, we begin this Facebook integration with the Facebook app set up and configuration in their app dashboard. For the client-side, we will use the facebook-login-vuejs module.

Table of Contents:

The following tools, framework, libraries, and modules are required for this tutorial:

  1. Node.js
  2. Vue.js 2
  3. facebook-login-vuejs
  4. Terminal or Node.js Command Line
  5. IDE or Text Editor

Before moving to the steps, make sure you have installed the latest Node.js. To check it, type this command in the terminal or Node.js command line.

node -v
v10.15.1
npm -v
6.10.2
Setup a Facebook App

To setup, a Facebook App and get an App ID/Secret, go to Facebook Developers Dashboard. Login with your Facebook developer's account or credentials.

Click the + Add a New App button. Enter the display name (we use VuejsAuth name) then click the Create App ID button. Make sure to use the valid name allowed by Facebook Developers.

After checking the captcha dialog and click submit button, now, you can see App ID and Secret, write it to your notepad.

Click the Settings menu on the left menu then click Basic. Scroll down then click + Add Platform button then choose the website. Fill site URL with the callback URL for OAuth authentication callback URL, we are using this callback URL http://127.0.0.1:1337/auth/facebook/callback.

Click the Save Changes button and don't forget to write down the App ID and App Secret to your notepad.

Install Vue-CLI 4 and Create Vue.js App

Vue-CLI is standard tooling Vue.js development. It has the features out-of-the-box support for Babel, TypeScript, ESLint, PostCSS, PWA, Unit Testing & End-to-end testing, fully configurable without the need for ejecting, allows the community to build and share reusable solutions to common needs, create, develop and manage your projects through an accompanying graphical user interface, and instantly prototype new ideas with a single Vue file. To install Vue-CLI 3 type this command from the Terminal or Node command line.

sudo npm install -g @vue/cli
yarn global add @vue/cli

Next, check the version to make sure that you have the 3.x version of Vue-CLI.

vue --version
@vue/cli 4.0.5

Next, create a new Vue.js project with the name "vue-firebase-chat" by type this command.

vue create vue-facebook-login

For now, use the default for every question that shows up in the Terminal. Next, go to the newly created folder.

cd ./vue-facebook-login

To make sure that created Vue.js project working, type this command to run the Vue.js application.

npm run serve
yarn serve

You will see this page when open http://localhost:8080/ in the browser.

Install and Configure the facebook-login-vuejs Module

We will use an existing Vue.js Facebook login module/library found on the NPMJS with the name facebook-login-vuejs. To install it, type this command.

npm i facebook-login-vuejs

or

yarn add facebook-login-vuejs

Because now the Facebook Login force to use HTTPS only, we need to modify this Vue.js 2 app to run with HTTPS. Create a new file in the root of the Vue 2 project folder called vue.config.js then add these lines of server configuration.

module.exports = {
    devServer: {
        open: process.platform === 'darwin',
        host: '0.0.0.0',
        port: 8085, // CHANGE YOUR PORT HERE!
        https: true,
        hotOnly: false,
    },
}
Display Sign In With Facebook Button and Basic User Profile

Now, we will implement the Facebook Login in the Vue.js 2 application. Open and edit src/App.vue then replace the Vue content with these.

<template>
  <div id="app">
    <facebook-login class="button"
      appId="420905468863679"
      @login="onLogin"
      @logout="onLogout"
      @get-initial-status="getUserData"
      @sdk-loaded="sdkLoaded">
    </facebook-login>
    <div v-if="isConnected" class="information">
      <h1>My Facebook Information</h1>
      <div class="well">
        <div class="list-item">
          <img :src="picture">
        </div>
        <div class="list-item">
          <i>{{name}}</i>
        </div>
        <div class="list-item">
          <i>{{email}}</i>
        </div>
        <div class="list-item">
          <i>{{personalID}}</i>
        </div>
      </div>
    </div>
  </div>
</template>

Inside the

How to build a secure Grails 4 Application using Spring Security Core

How to build a secure Grails 4 Application using Spring Security Core

In this Grails 4 tutorial, we will show you how to build a secure Grails 4 application using Spring Security Core Plugin. We will add the login and register function to the Grails 4 application.

In this Grails 4 tutorial, we will show you how to build a secure Grails 4 application using Spring Security Core Plugin. We will add the login and register function to the Grails 4 application. The purpose of using the Spring Security plugin has simplified the integration of Spring Security Java (we have written this tutorial). The usage of this Grails 4 Spring Security plugin similar to Grails 2 or 3, but there's a lot of updates on the Spring Security code and its dependencies to match the compatibilities.

Table of Contents:

The flow of this tutorial is very simple. We have a secure Product list that only accessible to the authorized user with ROLE_USER and Product CRUD for the user with ROLE_ADMIN. Any access to this Product resource will be redirected to the Login Page as default (if no authorized user). In the login page, there will be a link to the registration page that will register a new user.

The following tools, frameworks, libraries, and dependencies are required for this tutorial:

  1. JDK 8
  2. Grails 4
  3. Grails Spring Security Core Plugin
  4. Terminal or Command Line
  5. Text Editor or IDE

Before starting the main steps, make sure you have downloaded and installed the latest Grails 4. In Mac, we are using the SDKMan. For that, type this command in the Terminal to install SDKMan.

curl -s https://get.sdkman.io | bash

Follow all instructions that showed up during installation. Next, open the new Terminal window or tab then type this command.

source "$HOME/.sdkman/bin/sdkman-init.sh"

Now, you can install Grails 4 using SDKMan.

sdk install grails 4.0.1

Set that new Grails 4 as default. To check the Grails version, type this command.

grails -version
| Grails Version: 4.0.1
| JVM Version: 1.8.0_92
Create Grails 4 Application

Same as previous Grails version, to create a new Grails application, simply type this command.

grails create-app com.djamware.gadgethouse

That command will create a new Grails 4 application with the name "gadgethouse" with the package name "com.djamware". Next, go to the newly created project folder then enter the Grails 4 interactive console.

cd ./gadgethouse
grails

In the Grails interactive console, type this command to run this Grails application for the first time.

grails> run-app

Here's the new Grails 4 look like.

Install Grails Spring Security Core Plugin

Now, we will install and configure Grails Spring Security Core Plugin. For the database, we keep H2 in-memory database (You can change to other relational database configuration). To install the Grails Spring Security Core Plugin, open and edit build.gradle then add this dependency in dependencies array.

compile "org.grails.plugins:spring-security-core:4.0.0.RC2"

Next, stop the running Grails application using this command in the Grails interactive console.

stop-app

Compile the Grails application, to install the Spring Security Core plugin.

compile
Create User, Role, and Product Domain Class

We will use the Grails s2-quickstart command to create User and Role domain class for authentication. Type this command in Grails interactive console.

s2-quickstart com.djamware User Role

That command will create User and Role domain class with the package name "com.djamware". Next, open and edit grails-app/domain/com/djamware/Role.groovy to add default field after the bracket closing when this domain calls.

String toString() {
  authority
}

Next, create a domain class using regular Grails command for Product.

create-domain-class com.djamware.Product

Next, we need to create an additional field in the user domain class. For that, open and edit grails-app/domain/com/djamware/User.groovy then add a fullName field after the password field.

String fullname

Also, add a constraint for that field.

static constraints = {
    password nullable: false, blank: false, password: true
    username nullable: false, blank: false, unique: true
    fullname nullable: false, blank: false
}

Next, open and edit grails-app/domain/com/djamware/Product.groovy then replace that domain class with these Groovy codes.

package com.djamware

class Product {

    String prodCode
    String prodName
    String prodModel
    String prodDesc
    String prodImageUrl
    String prodPrice

    static constraints = {
        prodCode nullable: false, blank: false
        prodName nullable: false, blank: false
        prodModel nullable: false, blank: false
        prodDesc nullable: false, blank: false
        prodImageUrl nullable: true
        prodPrice nullable: false, blank: false
    }

    String toString() {
        prodName
    }
}
Create CustomUserDetailsService

Because we have added a field in the previous User domain class, we need to create a custom UserDetails. Create a new Groovy file src/main/groovy/com/djamware/CustomUserDetails.groovy then add these lines of Groovy codes that add full name field to the Grails UserDetails.

package com.djamware

import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.GrantedAuthority

class CustomUserDetails extends GrailsUser {

   final String fullname

   CustomUserDetails(String username, String password, boolean enabled,
                 boolean accountNonExpired, boolean credentialsNonExpired,
                 boolean accountNonLocked,
                 Collection<GrantedAuthority> authorities,
                 long id, String fullname) {
      super(username, password, enabled, accountNonExpired,
            credentialsNonExpired, accountNonLocked, authorities, id)

      this.fullname = fullname
   }
}

Next, type this command in the Grails interactive console to create a new Grails service.

grails> create-service com.djamware.CustomUserDetails

Open that file then replace all Groovy codes with these codes.

package com.djamware

import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService
import grails.plugin.springsecurity.userdetails.NoStackUsernameNotFoundException
import grails.gorm.transactions.Transactional
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException

class CustomUserDetailsService implements GrailsUserDetailsService {

   /**
    * Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least
    * one role, so we give a user with no granted roles this one which gets
    * past that restriction but doesn't grant anything.
    */
   static final List NO_ROLES = [new SimpleGrantedAuthority(SpringSecurityUtils.NO_ROLE)]

   UserDetails loadUserByUsername(String username, boolean loadRoles)
         throws UsernameNotFoundException {
      return loadUserByUsername(username)
   }

   @Transactional(readOnly=true, noRollbackFor=[IllegalArgumentException, UsernameNotFoundException])
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

      User user = User.findByUsername(username)
      if (!user) throw new NoStackUsernameNotFoundException()

      def roles = user.authorities

      // or if you are using role groups:
      // def roles = user.authorities.collect { it.authorities }.flatten().unique()

      def authorities = roles.collect {
         new SimpleGrantedAuthority(it.authority)
      }

      return new CustomUserDetails(user.username, user.password, user.enabled,
            !user.accountExpired, !user.passwordExpired,
            !user.accountLocked, authorities ?: NO_ROLES, user.id,
            user.fullname)
   }
}

Next, register that new CustomUserDetailsService in the grails-app/conf/spring/resources.groovy.

import com.djamware.UserPasswordEncoderListener
import com.djamware.CustomUserDetailsService
// Place your Spring DSL code here
beans = {
    userPasswordEncoderListener(UserPasswordEncoderListener)
    userDetailsService(CustomUserDetailsService)
}
Override Login Auth View

We will customize the login page to make UI better and add a link to the Register page. For that, create a login folder under views then create an auth.gsp file in that folder. Open and edit grails-app/views/login/auth.gsp then add these lines of GSP HTML tags.

<html>
<head>
    <meta name="layout" content="${gspLayout ?: 'main'}"/>
    <title><g:message code='springSecurity.login.title'/></title>
</head>

<body>
    <div class="row">
      <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
        <div class="card card-signin my-5">
          <div class="card-body">
            <h5 class="card-title text-center">Please Login</h5>
            <g:if test='${flash.message}'>
                <div class="alert alert-danger" role="alert">${flash.message}</div>
            </g:if>
            <form class="form-signin" action="${postUrl ?: '/login/authenticate'}" method="POST" id="loginForm" autocomplete="off">
              <div class="form-group">
                  <label for="username">Username</label>
                <input type="text" class="form-control" name="${usernameParameter ?: 'username'}" id="username" autocapitalize="none"/>
              </div>

              <div class="form-group">
                  <label for="password">Password</label>
                <input type="password" class="form-control" name="${passwordParameter ?: 'password'}" id="password"/>
                <i id="passwordToggler" title="toggle password display" onclick="passwordDisplayToggle()">&#128065;</i>
              </div>

              <div class="form-group form-check">
                  <label class="form-check-label">
                      <input type="checkbox" class="form-check-input" name="${rememberMeParameter ?: 'remember-me'}" id="remember_me" <g:if test='${hasCookie}'>checked="checked"</g:if>/> Remember me
                </label>
              </div>
              <button id="submit" class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Sign in</button>
              <hr class="my-4">
              <p>Don't have an account? <g:link controller="register">Register</g:link></p>
            </form>
          </div>
        </div>
      </div>
    </div>
    <script type="text/javascript">
        document.addEventListener("DOMContentLoaded", function(event) {
            document.forms['loginForm'].elements['username'].focus();
        });
        function passwordDisplayToggle() {
            var toggleEl = document.getElementById("passwordToggler");
            var eyeIcon = '\u{1F441}';
            var xIcon = '\u{2715}';
            var passEl = document.getElementById("password");
            if (passEl.type === "password") {
                toggleEl.innerHTML = xIcon;
                passEl.type = "text";
            } else {
                toggleEl.innerHTML = eyeIcon;
                passEl.type = "password";
            }
        }
    </script>
</body>
</html>

Next, we will make this login page as default or homepage when the application opens in the browser. For that, open and edit grails-app/controllers/UrlMappings.groovy then replace this line.

"/"(view: "index")

With this line.

"/"(controller:'login', action:'auth')
Add User Info and Logout to the Navbar

Now, we have to implement POST logout to the Navbar. This logout button active when the user logged in successfully along with user info. For that, modify grails-app/views/layout/main.gsp then replace all GSP HTML tags with these.

<!doctype html>
<html lang="en" class="no-js">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>
        <g:layoutTitle default="Grails"/>
    </title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <asset:link rel="icon" href="favicon.ico" type="image/x-ico"/>

    <asset:stylesheet src="application.css"/>

    <g:layoutHead/>
</head>

<body>

<nav class="navbar navbar-expand-lg navbar-dark navbar-static-top" role="navigation">
    <a class="navbar-brand" href="/#"><asset:image src="grails.svg" alt="Grails Logo"/></a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" aria-expanded="false" style="height: 0.8px;" id="navbarContent">
        <ul class="nav navbar-nav ml-auto">
            <g:pageProperty name="page.nav"/>
            <sec:ifLoggedIn>
              <li class="nav-item dropdown">
                  <a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown">
                    <sec:loggedInUserInfo field='fullname'/>
                  </a>
                  <div class="dropdown-menu navbar-dark">
                    <g:form controller="logout">
                      <g:submitButton class="dropdown-item navbar-dark color-light" name="Submit" value="Logout" style="color:gray" />
                    </g:form>
                  </div>
              </li>
            </sec:ifLoggedIn>
        </ul>
    </div>

</nav>

<div class="container">
    <g:layoutBody/>
</div>

<div class="footer row" role="contentinfo">
    <div class="col">
        <a href="http://guides.grails.org" target="_blank">
            <asset:image src="advancedgrails.svg" alt="Grails Guides" class="float-left"/>
        </a>
        <strong class="centered"><a href="http://guides.grails.org" target="_blank">Grails Guides</a></strong>
        <p>Building your first Grails app? Looking to add security, or create a Single-Page-App? Check out the <a href="http://guides.grails.org" target="_blank">Grails Guides</a> for step-by-step tutorials.</p>

    </div>
    <div class="col">
        <a href="http://docs.grails.org" target="_blank">
            <asset:image src="documentation.svg" alt="Grails Documentation" class="float-left"/>
        </a>
        <strong class="centered"><a href="http://docs.grails.org" target="_blank">Documentation</a></strong>
        <p>Ready to dig in? You can find in-depth documentation for all the features of Grails in the <a href="http://docs.grails.org" target="_blank">User Guide</a>.</p>

    </div>

    <div class="col">
        <a href="https://grails-slack.cfapps.io" target="_blank">
            <asset:image src="slack.svg" alt="Grails Slack" class="float-left"/>
        </a>
        <strong class="centered"><a href="https://grails-slack.cfapps.io" target="_blank">Join the Community</a></strong>
        <p>Get feedback and share your experience with other Grails developers in the community <a href="https://grails-slack.cfapps.io" target="_blank">Slack channel</a>.</p>
    </div>
</div>

<div id="spinner" class="spinner" style="display:none;">
    <g:message code="spinner.alt" default="Loading&hellip;"/>
</div>

<asset:javascript src="application.js"/>

</body>
</html>

As you see, there are built in Grails Spring Security TagLib sec:ifLoggedIn and <sec:loggedInUserInfo field='fullname'/>. The <sec:loggedInUserInfo field='fullname'/> only working when you implementing CustomUserDetailsService.

Create Register Controller and View

Back to the Grails interactive console to create a controller for the Register page.

grails> create-controller com.djamware.Register

Open and edit that Groovy file then replace all Groovy codes with these codes that have 2 methods of Register landing page and register action.

package com.djamware

import grails.validation.ValidationException
import grails.gorm.transactions.Transactional
import grails.plugin.springsecurity.annotation.Secured
import com.djamware.User
import com.djamware.Role
import com.djamware.UserRole

@Transactional
@Secured('permitAll')
class RegisterController {

    static allowedMethods = [register: "POST"]

    def index() { }

    def register() {
        if(!params.password.equals(params.repassword)) {
            flash.message = "Password and Re-Password not match"
            redirect action: "index"
            return
        } else {
            try {
                def user = User.findByUsername(params.username)?: new User(username: params.username, password: params.password, fullname: params.fullname).save()
                def role = Role.get(params.role.id)
                if(user && role) {
                    UserRole.create user, role

                    UserRole.withSession {
                      it.flush()
                      it.clear()
                    }

                    flash.message = "You have registered successfully. Please login."
                    redirect controller: "login", action: "auth"
                } else {
                    flash.message = "Register failed"
                    render view: "index"
                    return
                }
            } catch (ValidationException e) {
                flash.message = "Register Failed"
                redirect action: "index"
                return
            }
        }
    }
}

Next, add the index.gsp inside grails-app/views/register/. Open and edit that file then add these lines of GSP HTML tags.

<html>
<head>
    <meta name="layout" content="${gspLayout ?: 'main'}"/>
    <title>Register</title>
</head>

<body>
    <div class="row">
    <div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
      <div class="card card-signin my-5">
        <div class="card-body">
          <h5 class="card-title text-center">Register Here</h5>
                    <g:if test='${flash.message}'>
                        <div class="alert alert-danger" role="alert">${flash.message}</div>
                    </g:if>
              <form class="form-signin" action="register" method="POST" id="loginForm" autocomplete="off">
                      <div class="form-group">
                          <label for="role">Role</label>
              <g:select class="form-control" name="role.id"
                    from="${com.djamware.Role.list()}"
                    optionKey="id" />
                </div>

            <div class="form-group">
                    <label for="username">Username</label>
              <input type="text" placeholder="Your username" class="form-control" name="username" id="username" autocapitalize="none"/>
            </div>

            <div class="form-group">
                          <label for="password">Password</label>
              <input type="password" placeholder="Your password" class="form-control" name="password" id="password"/>
            </div>

            <div class="form-group">
                          <label for="password">Re-Enter Password</label>
              <input type="password" placeholder="Re-enter password" class="form-control" name="repassword" id="repassword"/>
            </div>

                      <div class="form-group">
                          <label for="username">Full Name</label>
              <input type="text" placeholder="Your full name" class="form-control" name="fullname" id="fullname" autocapitalize="none"/>
            </div>

            <button id="submit" class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Register</button>
            <hr class="my-4">
            <p>Already have an account? <g:link controller="login" action="auth">Login</g:link></p>
          </form>
        </div>
      </div>
    </div>
  </div>
    <script type="text/javascript">
        document.addEventListener("DOMContentLoaded", function(event) {
            document.forms['loginForm'].elements['username'].focus();
        });
    </script>
</body>
</html>
Create the Secure Product CRUD Scaffolding

Now, we will make Product CRUD scaffolding and make them secured and accessible to ROLE_USER and ROLE_ADMIN. To create CRUD scaffolding, simply run this command inside Grails interactive console.

grails>generate-all com.djamware.Product

That command will generate controller, service, and view for a Product domain class. Next, open and edit grails-app/controllers/ProductController.groovy then add the Secure annotation like these.

package com.djamware

import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*
import grails.plugin.springsecurity.annotation.Secured

class ProductController {

    ProductService productService

    static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]

    @Secured(['ROLE_ADMIN', 'ROLE_USER'])
    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond productService.list(params), model:[productCount: productService.count()]
    }

    @Secured(['ROLE_ADMIN', 'ROLE_USER'])
    def show(Long id) {
        respond productService.get(id)
    }

    @Secured('ROLE_ADMIN')
    def create() {
        respond new Product(params)
    }

    @Secured('ROLE_ADMIN')
    def save(Product product) {
        if (product == null) {
            notFound()
            return
        }

        try {
            productService.save(product)
        } catch (ValidationException e) {
            respond product.errors, view:'create'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.created.message', args: [message(code: 'product.label', default: 'Product'), product.id])
                redirect product
            }
            '*' { respond product, [status: CREATED] }
        }
    }

    @Secured('ROLE_ADMIN')
    def edit(Long id) {
        respond productService.get(id)
    }

    @Secured('ROLE_ADMIN')
    def update(Product product) {
        if (product == null) {
            notFound()
            return
        }

        try {
            productService.save(product)
        } catch (ValidationException e) {
            respond product.errors, view:'edit'
            return
        }

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.updated.message', args: [message(code: 'product.label', default: 'Product'), product.id])
                redirect product
            }
            '*'{ respond product, [status: OK] }
        }
    }

    @Secured('ROLE_ADMIN')
    def delete(Long id) {
        if (id == null) {
            notFound()
            return
        }

        productService.delete(id)

        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.deleted.message', args: [message(code: 'product.label', default: 'Product'), id])
                redirect action:"index", method:"GET"
            }
            '*'{ render status: NO_CONTENT }
        }
    }

    protected void notFound() {
        request.withFormat {
            form multipartForm {
                flash.message = message(code: 'default.not.found.message', args: [message(code: 'product.label', default: 'Product'), params.id])
                redirect action: "index", method: "GET"
            }
            '*'{ render status: NOT_FOUND }
        }
    }
}

Next, we will make Product controller as a default landing page after succeful login. For that, open and edit grails-app/conf/application.groovy then add this configuration.

grails.plugin.springsecurity.successHandler.defaultTargetUrl = '/product'
Run and Test Grails 4 Spring Security Core

Now, we will run and test the Grails 4 Spring Security Core application. In the Grails interactive console run this command.

grails>run-app

Open the browser then go to http://localhost:8080 and you will see this page.




That it's, the Grails 4 Tutorial: Spring Security Core Login Example. You can find the full working source code in our GitHub.

Thanks!

Facebook Login using Oauth2 authentication and Nodejs

Facebook Login using Oauth2 authentication and Nodejs

In this article, you will have a clear understanding of how to use oauth2 authentication to implement facebook login with node js.

In this article, you will have a clear understanding of how to use oauth2 authentication to implement facebook login with node js. Adding social login to your application has a lot of advantages. First of all, users of your application don't need to fill up a long registration form containing 10 or even more input fields.

Also while attempting to login to any application, they often forget their password. They, don't want to go through the password recovery process, as they find it tedious to do so.

The solution to this problem is if are able to register and login users to our application with their social network accounts in which they already have accounts. We can implement this functionality with the help of an authentication scheme known as Oauth2.

What is Oauth2

As per the official site of Oauth: OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 supersedes the work done on the original OAuth protocol created in 2006. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

In simple words, it is an authentication and authorization scheme in which users on the internet are able to access their information on other websites, without providing their account credentials ( username and/or password).

Only one requirement exists; that is, the user must authorize the application to access their data for a selected OAuth provider.

Why OAuth2 is Used

  • Users don't need to remember their credentials

Users can sign up or log in to any application that are using OAuth2 without using any credentials such as email id and/or password. They simply need to authorize the application to access their information for a selected OAuth provider. This step is done for one-time only.

  • Prevents security holes

In the Oauth2 mechanism, the user doesn't provide passwords to sign in or sign up for the application. So, from the development point of view, developers don't need to store a user's password. This, in fact, prevents inappropriate use of storing passwords.

  • Developer friendly

Developers can easily implement oauth2 in an application. They just need to go through the technical documentation for the specific OAuth provider. For example, if sign in and/or sign up with facebook functionality needs to be implemented, the developer needs to visit the official docs page for facebook OAuth provider.

  • Ability to handle non-web clients

In the OAuth2 authorization process, the program that sends requests to the authorization server is known as the client. The client can be a browser, a mobile app or any other device. That's how OAuth2 is able to handle non-web clients also.

How OAuth2 Works

Before discussing how OAuth2's working principle, it would be better if we discuss the key roles played by each component in this protocol.

  1. Resource Owner: It refers to the user who gives permission to authorize an application in order to access their account. The authorization's scope determines the application's access to the user's account.

  2. Resource or Authorization Server: The authorization server is responsible for verifying the identity of the user. Resource server refers to a server that hosts the protected user's accounts.

  3. Client: It refers to the application that accesses the user's account. But, in order to do so, it must be authorized by the user, and that authorization process must go through a validation process carried by an API.

Now, you know the roles played by each component; let's discuss the overall workflow of OAuth2 in simple words.

  • The client or the application sends requests for authorization to access resources from the resource server.

  • If the user accepts the request, the application receives permission to access the user's data as per the scope of the permission.

  • The client requests an access token from the authorization server or API representing the authenticity of its own identity. These access tokens life span is very short, think of their life span in terms of hours and minutes.

  • If the authorization server authenticates the application's identity, then the server generates an access token to the application.

  • The application requests the resource from the resource server or API. Then it sends the access token to the server for authentication.

  • If the resource server finds that the access token is valid then it serves the resource to the application.

You need to register your application before using OAuth2 with it. It can be done by visiting the service's website's developer portion. The following details are required in order to do this.

  1. Application Name

  2. Application Website

  3. Callback or redirect URL

What is Redirect URL in OAuth2

Redirect URL means where the service will redirect users after they authorize or deny your application. It also points to the route where you will write codes to handle access tokens.

What is Client Id in OAuth2

After registering the application, the service issues client credentials in the form of client id that is nothing but a unique string to identify the application and it is used by the service itself. Also, it helps to create an authorization URL that is displayed to the users.

What is Client Secret in OAuth2

The client secret's role is to authenticate the identity of the application to the service API when the application requests to access a user's account. The value of the client secret must be kept as a secret and should not be disclosed to anyone.

What is Refresh Token in OAuth2

We already discussed that the access token has a very short life-span. When the access token expires then refresh token enables the client to reauthorize without asking the resource owner to reauthenticate.

Alright, we discussed basics about what OAuth actually is, why we need it and what is the internal working principle behind OAuth2. Let's get down to building a node js application having facebook login built inside it that uses OAuth protocol.

Creating OAuth2 Facebook Application

At first, we need to create a facebook application, to do so visit the facebook developers page. Then log in with your Facebook account, this step is necessary because after doing this you will be able to get an application id and application secret that is mandatory for connecting our node js application with Facebook.

  1. After login click on the "Get Started" button, then you will something similar as shown in the screenshot below.

  1. Click on the "Next" button, then you need to choose your job role. Choose "Developer" (recommended).

  1. You need to create an app first, the screen-shot for this step is shown below.

  1. Click on "I am not a robot" option checkbox.

  1. After this step, you will be redirected to the "Add Product" page. On that page, click on the "Setup" button.

  1. Then you need to choose the platform for which you want to add facebook login functionality. Select "www" option.

  1. Then you need to enter the URL of your website. If you do not have a site in production, you can definitely use "localhost". I used "http://localhost:8000" for my this application. Click the "Save" button.

  1. Then skip the rest of the steps, click the "Settings" option in the left-hand menu.

  1. In the Settings page, you need to add redirect URL in order to tell facebook where a user will be redirected back after authorization. Here, again I am using localhost for doing this. I have added "http://localhost:8000/auth/facebook/callback" as the redirect URL. Click on the "Save" Changes button.

  1. Then go to the main settings link in the top-left position. That is highlighted in the screen-shot shown below.

  1. You will see app id and app secret keys, copy these and paste them somewhere. We will need them later.

That's it, you have successfully created a facebook application which is the first step for integrating facebook login to the node js application that we will create.

Find out what is the next step?

OAuth2 Workflow For Facebook Login Application

Let's discuss the workflow of the application as per the above screenshot. To create the application we require 3 main parties involved. The first one is the angular application, second is the Facebook server itself and last but not least the server which will act as a REST API written in Express JS Framework.

At first, users will try to login to our application. To do that they will click on the "Login With Facebook" button. Then a dialog will open that will ask the user to enter their Facebook credentials. Finally, the user gives permission to access some of their Facebook data.

After allowing access, our angular client gets the access token from the Facebook server. For now, we can easily access facebook data from the angular client application.

But, the backend server needs to know that. In order to do so, the angular application sends a request to the backend server with the access token. To verify that token the backend sends a verification request directly to the Facebook server.

If the Facebook server finds the token to be a valid one, it sends back the user's profile information. After receiving that data, the backend express js server verifies that the user profile data is correct and finally creates a new user in the application.

If the user already exists in the backend, the user profile will be updated instead.

Then the backend server will create a JSON web token that will identify the user. Backend returns that token as a response to the client application. The client app will store that token so that when sending requests to the server it can send the token along with the request.

What We Will Be Building

We will create an application that will have a login with facebook functionality. In order to understand the overall functioning of this app, you need to have fundamental knowledge in Angular and Node JS.

Then make sure you install node js and MongoDB. After downloading is finished extract the rar file, then open two command prompts or terminal windows. In one terminal navigate to the "frontend" folder. In another one, navigate to the "backend" folder. You need to start your MongoDB database too.

Open ".env" file in "backend" folder, put actual values in "FACEBOOK_APP_ID" and in "FACEBOOK_APP_SECRET" environmental variables. To get those values you need to put your app id and app secret keys that were generated when you created a Facebook application on facebook developer's website.

You may have to change other values as per your needs. For example, if you want to change the database name, you can do so by changing the value for "DB_DATABASE" variable.

The terminal where you opened the "frontend" folder run this command "npm start". In another terminal where the "backend" folder is opened, run "npm run dev-server".

Building The Frontend With Angular

Let's start building the application's frontend part with Angular. To connect our angular app with Facebook, we need to use Facebook's Javascript SDK.

For this we need to add the link to that SDK, we can do so with the help of script tag in index.html file as shown below.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Frontend</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>

  <!-- facebook javascript sdk -->
  <script src="//connect.facebook.net/en_US/sdk.js"></script>

</body>
</html>

Adding Bootstrap To The Project

Open another terminal, navigate to the location of the "frontend" folder. Run "npm install bootstrap" command, this will install bootstrap locally. Also, you need to add font-awesome for adding facebook icon to the login button.

Keep that terminal open, we will need this terminal when we will build our angular application. For doing this, run "npm install font-awesome". Then add that dependency in the angular.json file as shown below in the code snippet.

Creating Login Component For Our OAuth2 Facebook Application

When we will run our application, the user will see the login page. For that purpose, we need to create a login component. Run "ng g c login" in the terminal window. Open login.component.html file and add the following codes for designing the login component.

<div class="container">
    <div class="row">
      <div class="col-md-12 custom-card">
          <div class="card text-center">

              <div class="card-body">
                <h5 class="card-title">Log In With Facebook</h5>
                <p class="card-text">Log in with your existing facebook account</p>
                <button class="btn btn-primary fb-btn" (click)="fbLogin()"><i class="fa fa-facebook-square fa-2x" aria-hidden="true"></i> Login With Facebook</button>
              </div>
            </div>
      </div>

    </div>
  </div>

In the above code snippet, fbLogin() method is called when the "Login with Facebook" button is clicked. Let's write what will happen when that button will be clicked.

import { Component, OnInit } from '@angular/core';
import { UserService } from '../user.service';
import { Router } from '@angular/router';
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  constructor(
      private userService: UserService,
      private router: Router
  ) { }

  ngOnInit() {
  }

  fbLogin() {
    this.userService.fbLogin().then(() => {
      console.log('Called service from login component');
      // console.log(response);
      this.router.navigate(['dashboard']);
    });
  }

}

In the above code snippet, the fbLogin() method calls user service that performs an API call to our backend server and returns the promise object. After getting that promise object, the user is redirected to the dashboard page.

Creating User Service For Our OAuth2 Facebook Application

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

declare const FB: any;

@Injectable({
  providedIn: 'root'
})
export class UserService {

  constructor(private http: HttpClient) {
    FB.init({
      appId :  'YOUR_FACEBOOK_APP_ID',
      status : false,
      cookie : false,
      xfbml  : false,
      version : 'v4.0'
    });
   }

  fbLogin() {
    return new Promise((resolve, reject) => {

      FB.login(result => {
        if (result.authResponse) {
          return this.http
            .post(`http://localhost:8000/users/auth/facebook`, {access_token: result.authResponse.accessToken})
            .toPromise()
            .then(response => {
            const token = response;
            if (token) {
              localStorage.setItem('id_token', JSON.stringify(token));
            }
            resolve(response);
            })
            .catch(() => reject());
        } else {
          reject();
        }
      }, { scope: 'public_profile,email' });
    });
  }

  isLoggedIn() {
    return new Promise((resolve, reject) => {
      this.getCurrentUser().then(user => resolve(true)).catch(() => reject(false));
    });
  }

  getCurrentUser() {
    return new Promise((resolve, reject) => {
      return this.http.get(`http://localhost:8000/api/auth/me`).toPromise().then(response => {
        resolve(response);
      }).catch(() => reject());
    });
  }

  logout() {
    localStorage.removeItem('id_token');
    localStorage.clear();
  }

}

This user service will communicate with the Facebook server and our backend server. This service is responsible for performing the following tasks.

  • Making sure so that users can log in with their Facebook profile.

  • Logging users out.

  • Checking if users are logged in or not.

  • Getting details of currently logged in users.

To create the service issue this command in terminal. "ng g s user".

Explanation of the Code Snippet

In the UserService typescript class, a library is initialized from the facebook javascript SDK. Here, we need to replace "YOUR_FACEBOOK_APP_ID" with the application id that we got when we created the Facebook application on the facebook's developers website.

In fbLogin method, we are calling FB.login method that will display a dialog window, so that users can log in. If users are not logged in this dialog will be displayed. This dialog also asks users to allow the application to access user's data.

The response coming from FB.login method contains information whether the user is logged in or not and if they have allowed our application to access their data.

After getting response we call our backend to log in to the application. If user is able to log in to the backend, we will get a token as a response from the backend server.

We stored that token in local storage. So that, later when we will send a request to the backend, we are able to send the token alongwith the request. The main role of the token is to identify the current user.

The getCurrentUser method gets the data of currently logged in user from the server.

The logout method removes the token from the browser's local storage.

Creating Dashboard Component For Our OAuth2 Facebook Application

Run "ng g c dashboard" in the terminal to create dashboard component. Code snippet for dashboard.component.html is shown below.

<div class="navbar navbar-default navbar-fixed-top">
  <ul class="nav navbar-nav navbar-right">
    <li role="menuitem"><a class="dropdown-item" (click)="logout()">Logout</a></li>
  </ul>
</div>

<div class="page-header"></div>

<div class="container">

  <div class="row">
    <div class="col-lg-8 col-md-7 col-sm-6">
      <div class="panel panel-default">
        <div class="panel-heading text-center">Our Awesome application</div>
        <div class="panel-body" align="center">
          Current User email: {{ currentUser.email }}
        </div>
      </div>
    </div>
  </div>
</div>

In the above code snippet, we are displaying currently logged in user's email address.

Let's write the code for getting currently logged in user's details. Code snippet for dashboard.component.ts file is displayed below.

import { Component, OnInit } from '@angular/core';
import { UserService } from '../user.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  public currentUser: any = {};

  constructor(private userService: UserService, private router: Router) { }

  ngOnInit() {
    this.userService.getCurrentUser().then(profile => this.currentUser = profile)
        .catch(() => this.currentUser = {});
  }

  logout() {
    this.userService.logout();
    this.router.navigate(['/login']);
  }

}

In the code snippet, at the initialization phase of the dashboard component, we are loading user's data. We do so by calling the user service's getCurrentUser method inside ngOnInit method. After that we store user's data in currentUser object.

I guess, you remembered this currentUser object, it is used in dashboard component's html page to access currently logged in user's email address.

In the logout method, we are calling user service's logout method. After user is successfully logged out, they are redirected to the "login" route.

Creating Guards For Our OAuth2 Facebook Application

Let's assume we want to implement some sort of functionality such that we will allow only those users to visit the dashboard page who are already logged in.

We won't allow users who are not logged in and will redirect them to the login page when they will try to visit the dashboard page.

To add this functionality to an angular application, a guard is used.

There exist four types of guards in angular, these are as follows.

  1. CanActivate: This guard decides whether a route can be activated or not. If this guard returns true navigation will continue to otherwise navigation will not continue to the next route.

  2. CanActivateChild: It decides if a child route can be activated.

  3. CanDeactivate: It is helpful to decide if a route can be deactivated.

  4. CanLoad: It helps to decide whether a module can be lazy-loaded or not.

We need to add two guards in this application.

To create the auth guard type "ng g g auth" in the terminal window. The code snippet for AuthGuard is below.


import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router } from '@angular/router';
import { UserService } from './user.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private userService: UserService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.checkLogin();
  }

  checkLogin(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      this.userService.isLoggedIn().then(() => {
          resolve(true);
      }).catch(() => {
          this.router.navigate(['/welcome']);
          reject(false);
      });
    });
  }

}

In the above snippet, AuthGuard checks if the user is logged in or not. This is possible with the help of UserService's isLoggedIn method. If the user is logged in, we will resolve the promise, and allow the user to visit the dashboard page.

Otherwise, the user will be redirected to the login page.

Similarly to create another guard named anonymous, type "ng g g anonymous" in the terminal. The code snippet for it is displayed below.

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router } from '@angular/router';
import { UserService } from './user.service';

@Injectable({
  providedIn: 'root'
})
export class AnonymousGuard implements CanActivate {
  constructor(private userService: UserService, private router: Router) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.checkLogin();
  }

  checkLogin(): Promise<boolean> {
    return new Promise((resolve, reject) => {
        this.userService.isLoggedIn().then(() => {
            this.router.navigate(['/dashboard']);
            reject(false);
        }).catch(() => {
            resolve(true);
        });
    });
  }

}

In the code above, AnonymousGuard is used for checking if the user is not logged in. Its functionality is defined in UserService's isLoggedIn method. If the user is logged in the user will be redirected to the dashboard page.

Defining Routes For Our OAuth2 Facebook Application

import { AuthGuard } from './auth.guard';
import { AnonymousGuard } from './anonymous.guard';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './login/login.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent,
    canActivate: [AnonymousGuard]
  },
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuard]
  },
  {
    path: '',
    redirectTo: 'login',
    pathMatch: 'full'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

In the route file, we define what component angular will load when a specific route is being accessed by the user. For example, for visiting the login route the LoginComponent will load. When a user visits the application without any path, in that scenario, the LoginComponent will be loaded by default.

Explaining The AppModule

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';

import { JwtModule } from '@auth0/angular-jwt';
import { HttpClientModule } from '@angular/common/http';

export function tokenGetter() {
  return localStorage.getItem('id_token');
}

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    DashboardComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    JwtModule.forRoot({
      config: {
        tokenGetter,
        headerName: 'x-auth-token'

      }
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

In the above code snippet, we have used a new module named "auth0/angular-jwt", so that we can automatically attach a JSON web token as an authorization header. The browser attaches this header when the application sent the HTTP request.

The main role of tokenGetter function is to get the JSON web token from the of the current user from the browser's local storage. Angular fetches this token with the key "id_token".

Building The Backend With Express JS

Let's create the backend part of our application. We will be using the Express Js framework for creating the REST API. For storing user information we will use a MongoDB database.

Project Dependencies At a Glance

We are using the lightweight non-opinionated framework of Node i.e, Express Js. The body-parser module will take care of handling incoming request bodies with a middleware. The "jsonwebtoken" module will handle the JSON web token.

"passport" module will take care of authentication and "passport-facebook-token" will specifically handle the Facebook authentication. "mongoose" will communicate with MongoDB database. The "dotenv" module facilitates the use of environmental variables and the "cors" module will help to enable CORS on our server.

Creating The Node Server

const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const expressJwt = require('express-jwt');
require('dotenv').config();
const router = express.Router();
const cors = require('cors');
const User = require('./models/user');

// mongoose connection defined as IIFE( immediately invoked function expression)
(async function() {
    try {
        await mongoose.connect(`mongodb://${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_DATABASE}`, { useNewUrlParser: true, useUnifiedTopology: true });
        console.log('Connected to mongodb successfully');
    } catch(error) {
        console.log('Error connecting to mongodb');
    }
})();

const app = express();

const corsOption = {
    origin: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
    credentials: true,
    exposedHeaders: ['x-auth-token']
};
app.use(cors(corsOption));
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json());

// middleware for handling token
const authenticate = expressJwt({
    secret: process.env.EXPRESS_JWT_SECRET,
    requestProperty: 'auth',
    getToken: (req) => {
        if(req.headers['x-auth-token']) {
            return req.headers['x-auth-token'];
        }
        return null;
    }
});

const getCurrentUser = async (req, res, next) => {
    try {   
        const user = await User.findById(req.auth.id);
        req.user = user;
        next();
    } catch(error) {
        next(error);
    }
};

const getSingle = (req, res) => {
    const user = req.user.toObject();
    delete user['facebook'];
    delete user['__v'];
    res.json(user);
};

app.use('/users', require('./routes/users'));

router.route('/auth/me')
      .get(authenticate, getCurrentUser, getSingle);

app.use('/api', router);

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

module.exports = app;

In the above code snippet, at first all the dependencies are declared, then while configuring the CORS middleware in line number 23, we make sure that the "x-auth-token" header is visible to the angular client.

This step is necessary otherwise our angular client would ignore this custom header. It is done with the "exposedHeaders" property.

To connect with the MongoDB database, in line number 12, we have used the IIFE (Immediately Invoked Function Expression). If you don't know what it is, you can know more about it here.

In line number 36, we want to validate JWT(JSON Web Token) in every frontend request. If we find that the JSON Web Token is valid, then "req.auth" will be set with the decoded JSON object. Later the middleware that will perform authorization will use this object.

In line number 47, the user data is fetched by user id, and then that user data is stored in the request object within the "user" property. Finally in line 57, to extract only selected data from the user object, we removed two properties namely "facebook" and "__v".

Creating The user Routes File

const express = require('express');
const router = express.Router();
const passport = require('passport');
var passportConfig = require('../config/passport');

//setup configuration for facebook login
passportConfig();

const userController = require('../controllers/users');

router.route('/auth/facebook')
      .post(passport.authenticate('facebookToken', { session: false }), userController.facebookOAuth);

module.exports = router;

In line number 8, we invoked the passportConfig function, which has the actual implementation of how passport js module will handle facebook login functionality.

In this file, we have defined the route where we have configured to use passport js's token-based strategy for authenticating with Facebook login. That's why, in line number 13, you will notice that we have set to authenticate with "facebookToken", we set "session" as false.

Then we invoked the userController's facebookOAuth function.

Creating The passport.js File

const passport = require('passport');
const facebookTokenStrategy = require('passport-facebook-token');
const User = require('../models/user');
module.exports = function () {
    passport.use('facebookToken', new facebookTokenStrategy({
        clientID: process.env.FACEBOOK_APP_ID,
        clientSecret: process.env.FACEBOOK_APP_SECRET
    }, async (accessToken, refreshToken, profile, done) => {
        try {

            const existingUser = await User.findOne({ 'facebook.id': profile.id });

            if(existingUser) {
                return done(null, existingUser);
            }

            const newUser = new User({
                method: 'facebook',
                facebook: {
                    id: profile.id,
                    email: profile.emails[0].value,
                    token: accessToken
                }
            });

            await newUser.save();
            done(null, newUser);

        } catch(error) {
            done(error, false);
        }
    }));
};

In this file, we are checking if any user exists in the database, if one user is found, we simply return the user object. Otherwise, we create a new user and then return that user object instead.

Creating users Controller File

const JWT = require('jsonwebtoken');
const User = require('../models/user');
const JWT_SECRET = process.env.JWT_SECRET;

createToken = auth => {
    return JWT.sign({
        id: auth.id
    }, JWT_SECRET, { expiresIn: 60 * 120 });
}

module.exports = {
    facebookOAuth: async (req, res, next) => {

        if(!req.user) {
            return res.send(401, 'User not authenticated');
        }

        req.token = createToken(req.user);
        res.setHeader('x-auth-token', req.token);
        res.status(200).json(req.token);
    }
};

In the above code snippet, we are storing the user's id in a token. That token is known as JSON Web Token (JWT). After generating JWT, we send it to the frontend (i.e, angular application). We send the token with the help of a custom header i.e, "x-auth-token".

Creating user Model File

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

var userSchema = new Schema({
    method: {
        type: String,
        enum: ['facebook'],
        required: true
    },
    facebook: {
        id: {
            type: String
        },
        email: {
            type: String
        },
        token: {
            type: String
        },
        select: false
    }
});

var User = mongoose.model('User', userSchema);

module.exports.User = User;

Conclusion

Finally, you have a complete application that enables users to login with their existing Facebook account. You have created that app that follows the OAuth2 protocol in order to build this application.

For developing the frontend part we used Angular. Then the frontend will communicate with a backend that is built using Express Js. If you find this article helpful, consider sharing this with others. Thank you!