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

Cross-origin image resource permissions (CORS enabled image)

The HTML specification document introduces the cr...

In-depth understanding of the implementation principle of require loader

Preface We often say that node is not a new progr...

Example code comparing different syntax formats of vue3

The default template method is similar to vue2, u...

Example of how to implement value transfer between WeChat mini program pages

Passing values ​​between mini program pages Good ...

HTML uses the title attribute to display text when the mouse hovers

Copy code The code is as follows: <a href=# ti...

Examples of preview functions for various types of files in vue3

Table of contents Preface 1. Preview of office do...

JavaScript adds prototype method implementation for built-in objects

The order in which objects call methods: If the m...

How to use resize to implement image switching preview function

Key Points The CSS resize property allows you to ...

2 reasons why html-css tag style setting does not work

1 CSS style without semicolon ";" 2 Tags...

JavaScript to add and delete messages on the message board

This article shares a small example of adding and...

jQuery implements ad display and hide animation

We often see ads appear after a few seconds and t...