Using FaceIO To Develop User Authentication Module

In the past development of web application information systems, user authentication is an indispensable function module. The user authentication function includes user registration and log in authentication. In the past development methods, the common way to realize the user authentication function module is to use email and SMS to verify. Now, many users’ computers are equipped with cameras, which can fully use the artificial intelligence technology of face recognition to achieve user authentication. I used the JavaScript library of FaceIO to implement user authentication in the web app project.

This article mainly introduces how to develop the user login module of the web application project through the third-party AI service interface. The source code of the web application project has been uploaded to GitHub and is based on the MIT protocol. There are no restrictions. 

This project is a simple and complete web microservice system. The project adopts the development method of separating the front and back ends and uses different project file paths. The front end uses React, the back end uses Java 1.8 and microservices with Spring Boot, and the database uses MySQL. The front end integrates FaceIO in React to realize the docking of AI.

Technology stack of WEB APP project
Operating System:Windows 10
Front-end: Node.js 18.0.0, React 18.2.0, FaceIO, CoreUI 4.3.1
Front-end development tool: WebStorm 2019
Back-end: Java 1.8, Spring Boot, JWT, Mybatis, Maven
Back-end development tool: IntelliJ IDEA 2019
Database: MySQL 5.7+

The source code of this web project includes the front-end, back-end, and database, and is a complete web application information system. The front-end development uses React, the back-end development uses Java and SpringBoot, and the database uses MySQL. The third-party AI service interface uses FaceIO. FaceIO provides an online JavaScript library that can be directly referenced in front-end code. After the FaceIO library is referenced in the front end, adding a small amount of code can easily achieve face authentication, and after integration with the back end, complete user authentication can be achieved. The front-end UI uses the free template of CoreUI.

The use of FaceIO is not restricted to browsers. It can run on any browser, including IE, Chrome, Firefox, and Safari. Moreover, all AI business processing is completed on the server of FaceIO, so FaceIO needs to have access to the camera on the user’s current browser.

How To Use FaceIO Library in the Front End React Framework

Step 1: Install and Configure the Node.js Environment

From Node.js official website, download the compressed package of the corresponding version of Node.js. The version I use is V18.0.0. If you want to run the open-source web project I released locally, you’d better also use V18.0.0. Because the iteration speed of the Node.js version is relatively fast, if other versions are used, the local JavaScript library used in this open-source web project may be incompatible and may fail to run.

After downloading the compressed package of Node.js, extract it into the English directory. Because I use IntelliJ IDEA to develop front-end React, I need to configure Node.js and NPM in IntelliJ IDEA and specify the installation directory of Node.js.

After configuring Node.js and NPM in IntelliJ IDEA, you can create a React App project through IntelliJ IDEA tools.

Step 2: Apply for Public ID in FaceIO

FaceIO provides an online JavaScript library. If you want to use the AI service provided by FaceIO, you need to apply for a Public ID for your APP first. Sign in, first register an account, and then create an application based on your project to get the Public ID. This Public ID needs to be written in the code of your React App project. When applying for a Public ID, FaceIO will provide your app with a free version of the Public ID, with a limit on the number of times of use.

Step 3: Use FaceIO in the React App Project

The external JavaScript library address is provided by FaceIO.

In the code of React App project, you can refer to it as follows. For example, in my WEB project file Login.js:

  const script = document.createElement('script');
  script.type="text/javascript";
  script.async = true;
  script.src="https://cdn.faceio.net/fio.js";
  document.head.appendChild(script);

After the introduction of fio.js, the definition constants are ready for use. The code looks like this:

  let myFaceIO;
  useEffect(()=>{
    //eslint-disable-next-line
    myFaceIO = new faceIO("fioab497");
  },[])

fioab497 is the public ID of my app after registration. You need to replace the public ID of your app.

Please note that in the hook function useEffect() of the above code React, there is a line of code //eslint-disable-next-line. If you have installed the eslint plug-in in your development environment, you need to add this line of code. Without this line of code, eslint detection will think there is an error. The error prompt is as follows:

ERROR in [eslint]
srcviewspagesloginLogin.js
  Line 52:20:  'faceIO' is not defined  no-undef

In the development environment, the web project will fail to run. So, you need to add this line of comment code to let eslint skip the detection of the next line of code.

FaceIO provides the face registration function enroll(). The code is used like this:

  const faceSignUp = async ()=>{
    myFaceIO.enroll({
      "locale": "auto"
    }).then(userInfo => {
      console.log("facialId: " + userInfo.facialId)
      console.log(userInfo);
      handleLogin(userInfo,"login/signUp");
    }).catch(errCode => {
      console.log(errCode);
    })
  }

In the above code, the function enroll() inputs data in JSON string format and passes it to the artificial intelligence interface of FaceIO. UserInfo is the data object returned by FaceIO after facial verification. handleLogin () is a user-defined function that is used to communicate with my back-end after receiving the data object returned by FaceIO. Of course, you can also set other custom functions to handle the data objects returned by FaceIO according to your development situation.

In the JSON string format data input by the function enroll(), you can also add custom JSON string data. The code looks like this:

  const faceSignUp = async ()=>{
    myFaceIO.enroll({
      "locale": "auto", // Default user locale
      "payload": {
        "user": 123456, 
        "email": "name@example.com"
        }
    }).then(userInfo => {
      console.log("facialId: " + userInfo.facialId)
      console.log(userInfo);
      handleLogin(userInfo,"login/signUp");
    }).catch(errCode => {
      console.log(errCode);
    })
  }

payload is the data node that FaceIO can return. In “payload,” you can add the JSON format data you need to return and customize the data content according to your development needs.

After calling the function enroll(), the browser will display the UI provided by FaceIO for human-computer interaction and will start the camera at the browser prompt. You need to click “Yes.” The UI interface provided by FaceIO will automatically confirm the user’s face in front of the current camera twice and ask the current user to confirm the setting of the PIN code twice. The PIN code entered will be used for face authentication.

FaceIO provides the face authentication function authenticate(). The code is used like this:

  const faceSignIn = async ()=>{
    myFaceIO.authenticate({
      "locale": "auto"
    }).then(userInfo => {
      console.log("facialId: " + userInfo.facialId)
      console.log(userInfo);
      handleLogin(userInfo,"login/signIn");
    }).catch(errCode => {
      console.log(errCode);
    })
  }

After calling the function authenticate(), the browser will display the UI interface provided by FaceIO for human-computer interaction, start the camera, and ask the current login user to enter the PIN code set during facial registration.

My custom function handleLogin () is passed to the backend based on the data returned by FaceIO. The back end receives the data, analyzes the data, and returns the results to the front end. The front end carries out the subsequent business logic. If the user authentication is successful, the backend will return the Token data to the front end. After the front end confirms that the login is successful, the page will jump to the Dashboard to complete the entire user authentication process.

After receiving the token data, the front end saves the token data in the session storage of the user’s current browser. I customize an item named “Authorization” in the session storage to store Token data. The function code for saving Token data is like this:

const setAuthorization = (Auth) => {
    sessionStorage.setItem('Authorization',Auth)
}

The function code for obtaining Token data is like this:

function getAuthorization () {
    let Author = sessionStorage.getItem('Authorization')
    if (Author === null) return ''
    return Author
}

I have set the automatic loading of Token data in the system. In the subsequent business processing, when accessing the backend API, the Token data will be automatically placed in the authorization of the request header. The code looks like this:

instanceForm.interceptors.request.use(
    (config) => {
        config.headers.authorization = getAuthorization()
        return config
    }
)

The front-end source code of my project can be downloaded from GitHub.

How To Complete User Authentication on the Backend

In the web project, I use SpringBoot as the backend, and the development language is Java 1.8. In the Spring framework, create a control layer class that handles user login authentication. This LoginController class is also an API interface for handling user login authentication. The code is like this:

package com.auto17.base.controller;
import ...
import ...
@RestController
@RequestMapping("/login")
@CrossOrigin
public class LoginController{
    protected final Logger logger = LoggerFactory.getLogger(LoginController.class);
    @Autowired
    private IAppUserService appUserService;
    @PostMapping("/signUp")
public AjaxResult signUp(@RequestBody JSONObject userInfo) {
   ...
    }
    @PostMapping("/signIn")
public AjaxResult signIn(@RequestBody JSONObject userInfo) {
   ...
    }
}

In the code above, @RequestMapping ("/login") represents the API path of the entire LoginController class.
@PostMapping ("/signUp") represents the API path of user registration, receives POST data requests, and the function signUp() is used to process user registration. The complete API path is /login/signUp.
@PostMapping ("/signIn") represents the API path for user authentication. It receives POST data requests. The function signIn() is used to process user authentication. The complete API path is /login/signIn.

In the function signUp() or signIn(), parse the JSON format data passed by the front-end React. Extract the value of nodefacialId from JSON data. FacialId is the unique identifier returned by FaceIO for successful user authentication. This ID is unique. In this web project, I judge users by “facialId” and identify users by facialId.

It should be noted that in this web project, the JSON format data is transmitted from the front end to the back end in clear text without data encryption. If you are using it, you can add security functions to encrypt data in JSON format. After the front end is encrypted, the message is transferred from the front end to the back end. After the back end is decrypted, the JSON format data is parsed. There are many encryption methods. I recommend the RSA algorithm. RSA algorithm is an asymmetric encryption and decryption algorithm. At the back end, that is, when users access the web, a pair of RSA keys, including the public key and private key, are generated at the server end, and the public key is provided to the front end. The front end uses the public key to encrypt the JSON format data, and then transmits it to the back end. The back end decrypts it according to the private key. After decryption, parse the JSON format data. This can maximize the protection of the value of “facialId” from being easily intercepted.

On the back end, after the user is successfully identified by facialId. I use the JWT method to keep the information of the current login user. The function getToken (AppUser user) in the class JWTUtils gets the token of the current user. I use the value of facialId as the signature key. The code looks like this:

public class JWTUtils {
    public static String getToken(AppUser user) {
        Calendar instance = Calendar.getInstance();
        //Token expiration time
        instance.add(Calendar.DATE, 1);
        JWTCreator.Builder builder = JWT.create();
        return builder.withAudience(String.valueOf(user.getUserId()))
                .withClaim("userId", user.getUserId())
                .withClaim("facialId", user.getFacialId())
                .withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(user.getFacialId()));
    }
}

After obtaining the token of the current login user, the backend will return the token data to the front end.

After user authentication is successful, the backend will receive Token data for user authentication in each subsequent API request. I have created an interceptor class JWTInterceptor to verify the Token data before each API request. The code looks like this

package com.auto17.base.security;
import com.auto17.base.domain.AjaxResult;
import com.auto17.faceLogin.domain.AppUser;
import com.auto17.faceLogin.service.IAppUserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class JWTInterceptor extends HandlerInterceptorAdapter {
    protected final Logger log = LoggerFactory.getLogger(JWTInterceptor.class);
    @Autowired
    private IAppUserService appUserService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("authorization");
        log.info("token="+token);
        if(StringUtils.isEmpty(token)){
            log.error("authorization is Empty");
            errorOut(response,"authorization is Empty");
            return false;
        }
        try {
            //get login user info
            String userNoString=JWTUtils.getAudience(token);
            log.info("userNoString="+userNoString);
            AppUser loginUser=appUserService.selectAppUserById(Long.valueOf(userNoString));
            if(loginUser!=null){
                JWTUtils.verify(token,loginUser.getFacialId());
                request.setAttribute("loginUser", loginUser);
            }else{
                errorOut(response,"check user fail");
            }
        } catch (Exception e) {
            errorOut(response,"check verify fail");
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private void errorOut(HttpServletResponse response,String msg){
        try {
            response.setHeader("Access-Control-Allow-Origin","*");
            response.setContentType("text/json; charset=utf-8");
            response.setHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS,DELETE");
            response.setHeader("Access-Control-Max-Age","3600");
            response.addHeader("Access-Control-Allow-Headers", "*");
            PrintWriter writer=response.getWriter();
            writer.print(AjaxResult.error(msg));
            writer.flush();
            writer.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

The back-end (server) source code of my project can be downloaded from GitHub.

In addition to providing easy use on the front end, FaceIO also provides REST APIs that allow you to manage applications from the back end. Each FACEIO application is automatically assigned an API key. You can retrieve this key from the Application Manager on the FaceIO Console. The API key allows you to programmatically manage applications from a dedicated back-end environment. We can easily implement it using the RestTemplate of SpringBoot. Because my project is a simple application, I didn’t use it in this respect. If you are interested, you can visit the online documentation of the rest API of FaceIO.

How to Design a Database

In my web project, the database uses MySQL. In order to cooperate with face recognition, a simple user table is designed. Create table SQL statement:

CREATE TABLE `app_user`  (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `facial_id` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `nick_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `true_name` varchar(160) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `age` smallint(6) DEFAULT NULL,
  `gender` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `reg_time` datetime(0) DEFAULT NULL,
  `last_login_time` datetime(0) DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE,
  UNIQUE INDEX `idx_facialid`(`facial_id`) USING BTREE
)

The field facial_id in the data table is used to store the unique user ID returned by FaceIO. This is a simple user data table. There is no user password field or user mobile phone field. This user data table only uses the field “facial_id” to complete login user authentication and identification.

The database creation SQL script of my project can be downloaded from GitHub.

After the user completes registration from FaceIO, the JSON string data returned by FaceIO contains the “details” node, which is the age and gender of the current user identified by the artificial intelligence of FaceIO. I store it in the “age” and “gender” fields in the user data table.

This is my current development method. Of course, you can also keep the previous development method of user authentication and still keep the login password and SMS authentication. On the basis of the original development method, you have added the facial authentication method of FaceIO. In your original user data table, add the field facial_id to bind the face recognition of artificial intelligence.


Source link