SMS verification code login function based on antd pro (process analysis)

SMS verification code login function based on antd pro (process analysis)

summary

Recently, I encountered a new requirement when developing a project using antd pro, which is to log in via SMS verification code on the login interface instead of using the previous login method such as username and password.

Although this method adds extra SMS fees, it does improve security a lot. Antd does not have a built-in countdown button.
However, ProForm components of antd pro do provide components related to SMS verification codes.
Component description can be found at: https://procomponents.ant.design/components/form

Overall process

The process of logging in via SMS verification code is very simple:

  1. Request SMS verification code (client)
  2. Generate SMS verification code and set the expiration time of the verification code (server side)
  3. Call SMS interface to send verification code (server side)
  4. Log in using the verification code received via SMS (client)
  5. Verify the mobile phone number and SMS verification code, and issue a jwt-token after verification (server side)

front end

Page Code

import React, { useState } from 'react';
  import { connect } from 'umi';
   import { message } from 'antd';
  import ProForm, { ProFormText, ProFormCaptcha } from '@ant-design/pro-form';
 import { MobileTwoTone, MailTwoTone } from '@ant-design/icons';
  import { sendSmsCode } from '@/services/login';
 
 const Login = (props) => {
    const [countDown, handleCountDown] = useState(5);
    const { dispatch } = props;
    const [form] = ProForm.useForm();
    return (
      <div
        style={{
          width: 330,
          margin: 'auto',
        }}
      >
        <ProForm
          form={form}
          submitter={{
            searchConfig: {
              submitText: 'Login',
            },
            render: (_, dom) => dom.pop(),
            submitButtonProps: {
              size: 'large',
              style: {
                width: '100%',
              },
            },
            onSubmit: async () => {
              const fieldsValue = await form.validateFields();
              console.log(fieldsValue);
              await dispatch({
                type: 'login/login',
                payload: { username: fieldsValue.mobile, sms_code: fieldsValue.code },
              });
            },
          }}
        >
          <ProFormText
            fieldProps={{
              size: 'large',
              prefix: <MobileTwoTone />,
            }}
            name="mobile"
            placeholder="Please enter your phone number"
            rules={[
              {
                required: true,
                message: 'Please enter your phone number',
              },
              {
                pattern: new RegExp(/^1[3-9]\d{9}$/, 'g'),
                message: 'The phone number format is incorrect',
              },
            ]}
          />
          <ProFormCaptcha
            fieldProps={{
              size: 'large',
              prefix: <MailTwoTone />,
            }}
            countDown={countDown}
            captchaProps={{
              size: 'large',
            }}
            name="code"
            rules={[
              {
                required: true,
                message: 'Please enter the verification code! ',
              },
            ]}
            placeholder="Please enter the verification code"
            onGetCaptcha={async (mobile) => {
              if (!form.getFieldValue('mobile')) {
                message.error('Please enter your phone number first');
                return;
              }
              let m = form.getFieldsError(['mobile']);
              if (m[0].errors.length > 0) {
                message.error(m[0].errors[0]);
                return;
              }
              let response = await sendSmsCode(mobile);
              if (response.code === 10000) message.success('Verification code sent successfully!');
              else message.error(response.message);
            }}
          />
        </ProForm>
      </div>
    );
  };
  
  export default connect()(Login);

Request verification code and login service (src/services/login.js)

import request from '@/utils/request';

  export async function login(params) {
  return request('/api/v1/login', {
     method: 'POST',
     data: params,
   });
 }
 
  export async function sendSmsCode(mobile) {
    return request(`/api/v1/send/smscode/${mobile}`, {
      method: 'GET',
    });
  }

Model that handles login (src/models/login.js)

import { stringify } from 'querystring';
 import { history } from 'umi';
  import { login } from '@/services/login';
 import { getPageQuery } from '@/utils/utils';
 import { message } from 'antd';
  import md5 from 'md5';
 
  const Model = {
   namespace: 'login',
    status: '',
    loginType: '',
    state: {
      token: '',
    },
    effects:
      *login({ payload }, { call, put }) {
        payload.client = 'admin';
        // payload.password = md5(payload.password);
        const response = yield call(login, payload);
        if (response.code !== 10000) {
          message.error(response.message);
          return;
        }
  
        // set token to local storage
        if (window.localStorage) {
          window.localStorage.setItem('jwt-token', response.data.token);
        }
  
        yield put({
          type: 'changeLoginStatus',
          payload: { data: response.data, status: response.status, loginType: response.loginType },
        }); // Login successfully
  
        const urlParams = new URL(window.location.href);
        const params = getPageQuery();
        let { redirect } = params;
  
        console.log(redirect);
        if (redirect) {
          const redirectUrlParams = new URL(redirect);
  
          if (redirectUrlParams.origin === urlParams.origin) {
            redirect = redirect.substr(urlParams.origin.length);
  
            if (redirect.match(/^\/.*#/)) {
              redirect = redirect.substr(redirect.indexOf('#') + 1);
            }
          } else {
            window.location.href = '/home';
          }
        }
        history.replace(redirect || '/home');
      },
  
      logout() {
        const { redirect } = getPageQuery(); // Note: There may be security issues, please note
  
        window.localStorage.removeItem('jwt-token');
        if (window.location.pathname !== '/user/login' && !redirect) {
          history.replace({
            pathname: '/user/login',
            search: stringify({
              redirect: window.location.href,
            }),
          });
        }
      },
    },
    reducers: {
      changeLoginStatus(state, { payload }) {
        return {
          ...state,
          token: payload.data.token,
          status: payload.status,
          loginType: payload.loginType,
        };
      },
    },
  };
  export default Model;

rear end

The backend mainly has two interfaces, one for sending SMS verification codes and one for login verification.

Routing code snippet:

apiV1.POST("/login", authMiddleware.LoginHandler)
 apiV1.GET("/send/smscode/:mobile", controller.SendSmsCode)

SMS verification code processing

  1. There are a few points to note when processing SMS verification codes:
  2. Generate a random fixed-length number and call the SMS interface to send the verification code. Save the verification code for future verification.
  3. Generate fixed-length numbers

The following code generates a 6-digit number. If the random number is less than 6 digits, add 0 in front.

r := rand.New(rand.NewSource(time.Now().UnixNano()))
 code := fmt.Sprintf("%06v", r.Int31n(1000000))

Call SMS API

This is simple. Just call it according to the instructions of the purchased SMS interface.

Save the verification code for verification

It should be noted here that the verification code must have an expiration date, and one verification code cannot be used all the time.
The verification code for temporary storage can be placed in the database, or in a KV storage such as redis. For simplicity, the verification code is stored directly in the memory using a map structure.

package util

 import (
    "fmt"
   "math/rand"
   "sync"
  "time"
  )

  type loginItem struct {
    smsCode string
    smsCodeExpire int64
  }
  
  type LoginMap struct {
    m map[string]*loginItem
    sync.Mutex
  }
  
  var lm *LoginMap
  
  func InitLoginMap(resetTime int64, loginTryMax int) {
    lm = &LoginMap{
      m: make(map[string]*loginItem),
    }
  }
  
  func GenSmsCode(key string) string {
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    code := fmt.Sprintf("%06v", r.Int31n(1000000))
  
    if _, ok := lm.m[key]; !ok {
      lm.m[key] = &loginItem{}
    }
  
    v := lm.m[key]
    v.smsCode = code
    v.smsCodeExpire = time.Now().Unix() + 600 // The verification code expires in 10 minutes return code
  }
  
  func CheckSmsCode(key, code string) error {
    if _, ok := lm.m[key]; !ok {
      return fmt.Errorf("Verification code not sent")
    }
  
    v := lm.m[key]
  
    // Is the verification code expired? if time.Now().Unix() > v.smsCodeExpire {
      return fmt.Errorf("Verification code (%s) has expired", code)
    }
  
    // Is the verification code correct if code != v.smsCode {
      return fmt.Errorf("Verification code (%s) incorrect", code)
    }
  
    return nil
  }

Login verification

The login verification code is relatively simple, which is to first call the above CheckSmsCode method to verify whether it is legal.
After verification, obtain user information based on the mobile phone number, generate jwt-token and return it to the client.

FAQ

Antd version issue

To use ProForm of antd pro, you need to use the latest version of antd, preferably >= v4.8, otherwise there will be incompatible errors in the front-end components.

Points that can be optimized

The above implementation is relatively rough, and the following aspects can be further optimized:

The verification code needs to be sent less frequently. After all, sending SMS messages costs money. The verification code is directly in the memory and will be lost after the system restarts. You can consider putting it in a storage such as redis.

This is the end of this article about the SMS verification code login function (process analysis) based on antd pro. For more relevant antd pro verification code login content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • AntDesign Pro + .NET Core implements JWT-based login authentication function
  • Detailed explanation of how to implement login function by combining React with Antd's Form component

<<:  Detailed explanation of Linux file permissions and group modification commands

>>:  How to connect JDBC to MySQL 5.7

Recommend

MySQL index knowledge summary

The establishment of MySQL index is very importan...

Mysql example of splitting into multiple rows and columns by specific symbols

Some fault code tables use the following design p...

An example of refactoring a jigsaw puzzle game using vue3

Preface It took two days to reconstruct a puzzle ...

MySQL batch removes spaces in a certain field

Is there any way to remove spaces from a certain ...

Example of converting JS one-dimensional array into three-dimensional array

Today I saw a friend asking a question in the Q&a...

CSS sample code to achieve circular gradient progress bar effect

Implementation ideas The outermost is a big circl...

MySQL online DDL tool gh-ost principle analysis

Table of contents 1. Introduction 1.1 Principle 1...

Do you know how many connections a Linux server can handle?

Preface First, let's see how to identify a TC...

Detailed installation tutorial of Docker under CentOS

Docker is divided into CE and EE. The CE version ...

Overview of MySQL Statistics

MySQL executes SQL through the process of SQL par...

Detailed explanation of mysql partition function and example analysis

First, what is database partitioning? I wrote an ...

Solution for Tomcat to place configuration files externally

question When we are developing normally, if we w...

Example of creating circular scrolling progress bar animation using CSS3

theme Today I will teach you how to create a circ...

Summary of MySQL's commonly used concatenation statements

Preface: In MySQL, the CONCAT() function is used ...