Taro small program development large-scale combat (9): use Authing to create an enterprise-level user system with WeChat login

Posted May 26, 202025 min read

If you think we have written well, remember like + follow + comment Sanlian, encourage us to write better tutorials ?

Welcome to continue reading "Taro small program development large-scale combat" series, review the foreword:

Before, our applet had some essential functions for a simple blog:

  • Authority management:login is required before posting
  • Login:ordinary login and WeChat login, etc.
  • Post:Post will automatically bring user information
  • Get all posts and individual posts

At first glance, this blog is a little complete, but the classmates who follow along should know that our previous logins are logged in by passing in the user's nickName and photo, but we usually see it in life Some more regular websites or small programs, their logins are generally similar to mobile phone + verification code login, and in a standard blog, may also involve such as user rights management, user login status query, etc. The various user-related scenarios are generally abstracted as a core module in an application-the user system, that is, all content related to user registration/login, information update, rights management, authentication, etc.

In order to make our blog look more professional, we plan to add a professional user system to it. With the core user system, we can easily expand after the blog, but according to statistics, an application requires If you want to build a more professional user system, it will take at least a few months, and you will need to spend a lot of energy to maintain the user system you created. After doing some research, we have placed our goal on an object called Authing. Universal cloud identity platform, the service it provides is to help applications quickly integrate an efficient and secure user system, and our tutorial will explain how to use Authing to arm our professional applet blog with a professional user system.

First, let ’s take a look at the completed effect:

2020-04-30 15-25-51.2020-04-30 15_27_30.gif

Prepare new version of login logic

If you want to start directly from this article, you can Clone the code we prepared for you, and then follow the tutorial to supplement the rest:

git clone -b authing-start https://github.com/tuture-dev/ultra-club.git
# Or download the warehouse on Gitee
git clone -b authing-start https://gitee.com/tuture/ultra-club.git

Improve common login

First of all, let ’s improve the professionalism of the previous ordinary login. Previously, we let the user enter a nickname and upload an avatar and then log in. Now we plan to switch to the form of mobile phone number + verification code to immediately modernize.

Open the src/components/LoginForm/index.jsx file and make the corresponding changes to the contents as follows:

import Taro, {useState, useRef, useEffect} from '@ tarojs/taro'
import {View, Form} from '@ tarojs/components'
import {AtButton, AtImagePicker} from 'taro-ui'
import {useDispatch} from '@ tarojs/redux'

import {SET_IS_OPENED, SET_LOGIN_INFO} from '../../constants'
import CountDownButton from '../CountDownButton'
import './index.scss'

export default function LoginForm(props) {
  //Login Form login data
  const [phone, setPhone]= useState('')
  const [phoneCode, setPhoneCode]= useState('')
  const countDownButtonRef = useRef(null)

  const dispatch = useDispatch()

  async function countDownButtonPressed() {
    if(! phone) {
      Taro.atMessage({
        type:'error',
        message:'You have not filled in your phone!',
      })

      return
    }

    countDownButtonRef.current.startCountDown()

    //handle sending verification code event
  }

  async function handleSubmit(e) {
    e.preventDefault()

    //Authentication data
    if(! phone ||! phoneCode) {
      Taro.atMessage({
        type:'error',
        message:'You have nothing to fill in! ',
      })

      return
    }

    //Handle login and registration
  }

  return(
    <View className = "post-form">
      <Form onSubmit = {handleSubmit}>
        <View className = "login-box">
          <Input
            className = "input-phone input-item"
            type = "text"
            placeholder = "Enter mobile phone number"
            value = {phone}
            onInput = {e => setPhone(e.target.value)}
          />
          <View className = "verify-code-box">
            <Input
              className = "input-nickName input-item"
              type = "text"
              placeholder = "Four-digit verification code"
              value = {phoneCode}
              onInput = {e => setPhoneCode(e.target.value)}
            />
            <CountDownButton
              onClick = {countDownButtonPressed}
              ref = {countDownButtonRef}
            />
          </View>
          <AtButton formType = "submit" type = "primary">
            log in
          </AtButton>
          <View className = "at-article__info">
            Log in via phone and verification code, if there is no account, we will create
          </View>
        </View>
      </Form>
    </View>
 )
}

As you can see, the above code mainly has the following changes:

  • Removed useState logic for handling avatar and nickName
  • Removed the onImageClick and onChange functions used to process uploaded avatars, and the AtImagePicker component
  • Improved and added two input boxes, one for entering the mobile phone number and one for entering the verification code, which also adds the useState logic of phone and phoneCode
  • Improved handleSubmit, removed the original logic to handle nickName and files, and removed the dispatch logic to initiate login before
  • Then we added a CountDownButton component for counting down, and countDownButtonRef to get ref and countDownButtonPressed function to handle button click event, in the function we will do data verification, if the user fills in the mobile phone number To allow the execution of the countdown logic, in the next step we will process the mobile phone verification code sending logic in this function.
  • Finally, we added a copy that prompts the user to log in with the phone and verification code.

Tips
The CountDownButton here is the material provided by Taro's official material market. You can visit \ [this address ](), download the material, and then put the CountDownButton folder under the src/compontents folder. We also need to make a little modification to the style of this component to suit our current UI style. We will explain how to modify it immediately. Readers can download this material first and then place it in the project directory just mentioned.

Style improvements

We have improved the components above. In order to make our new login look more professional and unified, we add some styles, open the src/components/LoginForm/index.scss file, and make the corresponding changes to the contents as follows:

.post-form {
  margin:0 30px;
  padding:30px;
}

.input-item {
  border:1px solid #eee;
  padding:10px;
  font-size:medium;
  margin-top:40px;
  margin-bottom:40px;
}

.input-phone {
  width:100%;
}

.input-nickName {
  width:calc(100%-200px);
}

.verify-code-box {
  display:flex;
  flex-direction:row;
  align-items:center;
  justify-content:space-between;
}

.avatar-selector {
  width:200px;
  margin:0 auto;
}

Using Taro materials

The CountDownButton component we used above is a material in the Taro material market. Simply put, a material is a package or component that can be fully functional in certain aspects, helping developers quickly complete a certain logic without reinventing the wheel, as Official tagline of Taro Material Market:

Let each make every wheel produce value

Through the previous steps, we should have downloaded the materials and placed them under the src/components folder. You can see that there are mainly two files in the component, a logical file src/components/CountDownButton/index. js, there is also a style file src/components/CountDownButton/index.css Yes, here we need to make a small modification is that the logic file refers to the index.scss file, we need the suffix For index.scss.

Then, in order to integrate with our existing UI, we also changed the activeButtonStyle and buttonCommonStyle styles of the src/components/CountDownButton/index.scss file. The final file contents are as follows:

/* Button default display style * /

.buttonCommonStyle {
  margin:0;
  width:160px;
  height:calc(1.4rem + 20px);
  padding:0;
  display:flex;
  justify-content:center;
  align-items:center;
  line-height:1;
  border-radius:4px;
  border:none;
  outline:none;
}

/* TouchableOpacity style when disabled * /

.disableButtonStyle {
  background-color:# f6f6f6! important;
}

/* TouchableOpacity style when you can click * /

.activeButtonStyle {
  background-color:# 02b875;
}

/* Default style of text * /

.txtCommonStyle {
  font-size:20px;
}

/* Text style when disabled * /

.disableTxtStyle {
  color:# 999;
}

/* Text style when you can click * /

.activeTxtStyle {
  color:#fff;
}

You're done! ?, we have successfully completed the new version of the ordinary login interface. When you save the code and open the WeChat applet in the root directory through npm run dev:weapp, and use the developer tools to open our project, its effect It should be of the following type:

How about it, is it similar to the login registration interface and logic of various professional apps or websites and applets you have experienced before? ?After having such a professional login interface, we will next need to run the entire logic from the front end to the back end. Next, we will run the login and registration logic.

Tips
Here we have integrated the login and registration pages together. Through the small text prompt below the login button, if the user does not have an account, then after logging in through the mobile phone number and verification code, we will directly register an account for the user, simplifying the interface logic Behind the code usually needs to make a lot of improvements and optimizations in the code logic, but we will access the next general identity cloud platform-Authing to simplify this logic to a few lines of code.

Use Authing to access the complete user system

At the beginning of the article and at the end of the previous summary, I bought so many passes, saying that how Authing simplifies our development costs, some readers are estimated to be a bit impatient, is this Authing so convenient? , We will start to use it in depth in this section.

In order to connect Authing to our blog applet, we need to do the following preparations:

  • Register an Authing account and create a "small program" type user pool
  • Through the official documentation, find the small program SDK, and download the corresponding file to the project directory
  • Import the Authing applet SDK into the project code and start using

Register for an Authing account

Open Authing official website , we will see the following interface:

image.png

We click on the login in the upper right corner, you can see that it will pop up the following interface:

We can slow down at this time and take a good look at the companies that provide a universal identity cloud platform. What is their login interface? As you can see, we are familiar with WeChat login, email + password login, mobile phone number + verification code login, as well as Github login commonly used by technical developers, and even a relatively special small program scan code login, which basically connects us on the Internet The most efficient user login and registration function logic that may be used is integrated into a small form.

Readers can choose their own login method ?, here we choose WeChat login, and then use the WeChat QR code to log in on the pop-up scanning interface. After logging in, an interface will pop up for you to bind your mobile phone number, and readers can choose whether to bind or not. When this step is completed, the interface will take you to an interface for creating an application. We select the applet and click Next:

Next, you will be asked what your application is for, we fill in:"Tuque Community Blog Mini Program"(readers can make up their own brains here), and then we click next:

Then you will be set up a second-level domain name, we enter tuture-blog-miniprogram, and then we click to complete:

Next, we come back to an interface to quickly experience the Authing function. The system creates an account for you by default:

  • Account:test @ test.com
  • Password:123456a!

You can experience whether you can log in in the interface on the right. Of course, you can also register a user. The registered user can later see this registered user in the "Tuque Community Blog Mini Program" user pool we created :

And the above interface also explains how to quickly integrate the login function of Authing and check the login status, and gives the JS implementation code, and the Guard in the lower left corner. This Guard is simply a collection of the Authing registration we saw before. 1. The function of login form, and provide you with a professional interface, so that you can achieve a look similar to the official registration of Authing in a few lines of code. This is the interface we just saw:

Tips
We have described how to use it in the integrated user system of the full-stack e-commerce series of articles in the Tuque community. Interested readers can read this article.

Okay, we click "Know, enter the console" in the lower left corner, and start to enter our Authing user pool management console. Before that, you will also be asked to fill in a callback address. Yes, or you can fill in one at random, such as http://localhost:3000.

Finally, we came to such an interface:

You can see that the left side of this interface is the "user pool" management interface we have mentioned before. By default, the "Tuque community blog applet" that we just created is selected. A user pool is a whole set of users and login and registration with users. , Authentication, login status, active, permissions and other related logic.

In the middle are some introductions in the single user pool. For example, what we see now is a user login heat map similar to the heat map of Github. You can easily see how many user logins that day. Charts and content for user data analysis.

Download and configure SDK

After registering an account and establishing a user pool, we need to download the WeChat applet SDK provided by Authing to integrate the user system, click [this link]( https://docs.authing.cn/authing/sdk/authing-sdk- for-wxapp? utm_source = tuture) Go to the applet development documentation.

According to the documentation, we need to configure two domain name whitelists in the WeChat applet background:

  • [https://oauth.authing.cn](https://oauth.authing.cn) and [https://users.authing.cn](https://users.authing.cn)

Then fill in the corresponding address of Authing with the AppId and AppSecret of the WeChat applet:

This interface, then slide to the bottom, select the applet to log in:

In the pop-up box, fill in the corresponding AppId and AppSecret of the WeChat applet:

After configuration, we can download the SDK code and put it in our project, find a place(in a non-existing project), run the following script, Clone applet SDK:

$git clone https://github.com/Authing/authing-wxapp-sdk

Then open this project and copy the authing folder into the src/utils directory of our ultra-club applet. The final directory structure should look like this:

src
├── store
│ └── index.js
└── utils
    └── authing
        ├── authing.js
        ├── configs.js
        ├── graphql
        │ └── wxgql.js
        └── utils
            ├── qiniuUploader.js
            ├── util.js
            └── wxapp_rsa.min.js

The SDK development environment is ready✌️, we will immediately integrate the identity logic of the mobile phone number + verification code login!

Start integration

Open the src/components/LoginForm/index.jsx file and make the corresponding changes to the contents as follows:

import Taro, {useState, useRef, useEffect} from '@ tarojs/taro'
import {View, Form} from '@ tarojs/components'
import {AtButton, AtImagePicker} from 'taro-ui'
import {useDispatch} from '@ tarojs/redux'

import {SET_IS_OPENED, SET_LOGIN_INFO} from '../../constants'
import CountDownButton from '../CountDownButton'
import Authing from '../../utils/authing/authing'
import './index.scss'

export default function LoginForm(props) {
  //Login Form login data
  const [phone, setPhone]= useState('')
  const [phoneCode, setPhoneCode]= useState('')
  const countDownButtonRef = useRef(null)
  let userPoolId = ''

  const dispatch = useDispatch()

  async function countDownButtonPressed() {
    if(! phone) {
      Taro.atMessage({
        type:'error',
        message:'You have not filled in your phone!',
      })

      return
    }

    countDownButtonRef.current.startCountDown()

    try {
      const authing = new Authing({
        userPoolId,
      })
      const res = await authing.getVerificationCode(phone)

      if(res.code === 200) {
        Taro.atMessage({
          type:'success',
          message:'Mobile verification code has been successfully sent, please check it'
        })
      }
    } catch(err) {
      Taro.atMessage({
        type:'error',
        message:'Failed to send verification code, please try later!',
      })
      console.log('err', err)
    }
  }

  async function handleSubmit(e) {
    e.preventDefault()

    //Authentication data
    if(! phone ||! phoneCode) {
      Taro.atMessage({
        type:'error',
        message:'You have nothing to fill in! ',
      })

      return
    }

    try {
      const authing = new Authing({
        userPoolId,
      })

      const userInfo = await authing.loginByPhoneCode({
        phone,
        phoneCode,
      })

      //Prompt for successful login
      Taro.atMessage({
        type:'success',
        message:'Congratulations, you have logged in successfully! ',
      })

      const {nickname, photo, _id} = userInfo

      //Initiate a login request to the backend
      await Taro.setStorage({
        key:'userInfo',
        data:{
          nickName:nickname,
          avatar:photo,
          _id,
        },
      })

      await Taro.setStorage({
        key:'token',
        data:userInfo.token,
      })

      dispatch({
        type:SET_LOGIN_INFO,
        payload:{nickName:nickname, avatar:photo, userId:_id},
      })

      dispatch({type:SET_IS_OPENED, payload:{isOpened:false}})
    } catch(err) {
      Taro.atMessage({
        type:'error',
        message:'Login failed',
      })
    }
  }

  return(
    <View className = "post-form">
      <Form onSubmit = {handleSubmit}>
        <View className = "login-box">
          <Input
            className = "input-phone input-item"
            type = "text"
            placeholder = "Enter mobile phone number"
            value = {phone}
            onInput = {e => setPhone(e.target.value)}
          />
          <View className = "verify-code-box">
            <Input
              className = "input-nickName input-item"
              type = "text"
              placeholder = "Four-digit verification code"
              value = {phoneCode}
              onInput = {e => setPhoneCode(e.target.value)}
            />
            <CountDownButton
              onClick = {countDownButtonPressed}
              ref = {countDownButtonRef}
            />
          </View>
          <AtButton formType = "submit" type = "primary">
            log in
          </AtButton>
          <View className = "at-article__info">
            Log in via phone and verification code, if there is no account, we will create
          </View>
        </View>
      </Form>
    </View>
 )
}

As you can see, the above content has mainly been modified as follows:

  • We first introduced the Authing SDK downloaded in the previous step
  • Then we defined a userPoolId, which is the logo ID of the user pool we created earlier in the" Tuque Community Blog Mini Program ". Here the reader needs to go to the Authing console interface to obtain the user pool ID and replace the empty string above :

  • Next, we initiate the mobile phone verification code operation in the countDownButtonPressed function. We first use new Authing to pass in the user pool ID userPoolId to initialize an instance and name it authing. This step represents that we got this page The operation right of the user pool, then we can carry out user-related operations.
  • Then we use the authing.getVerificationCode method, passing in the filled mobile phone number phone, which is an asynchronous Promise object, so we use the await keyword to get its result, when the result res.code is 200, On behalf of the success of sending the verification code, we remind the user to send the verification code successfully, otherwise remind the failure to send the verification code, after writing the above code and saving, we can open the applet to try the effect, enter the mobile phone number, and click to send the verification code:

Of course, I input the phone number above blindly, readers please enter your own phone number to try, and then you should be able to receive a verification code message on your phone:

Boom?! You can see a few simple lines of code, and we are done sending the mobile phone verification code.

  • Next we need to perfect the logic of logging in using the mobile phone + verification code. We wrote a try/catch statement in handleSubmit, then initialized the Authing object, and called the method authing.loginByPhoneCode to pass into our mobile Number(phone) and verification code phoneCode, after calling, we have completed the login of the mobile phone number + verification code, this method defaults to the account creation operation for unlogged users, without requiring users to deal with other logic.
  • Next, we get the content through the userInfo returned after successful login, make changes and set it into storage, and store it in the Redux Store, and prompt the user to log in successfully. Of course, if the login fails, we will also prompt the user to login failed.

Tips

  1. Here we have adapted the data format, such as adapting the user information returned by Authing login userInfo.nickname to nickName, in order to match the data format of the previous applet system.
  2. It can be seen that we have saved an additional userInfo.token into storage, this token is a sign used for user authentication in our user system, and then we will use this token to check the user's login State and maintain the user login state.

Everything is ready, next we fill in the mobile phone number, click to obtain the verification code, and fill the verification code into the input box of the applet, click login, you should be able to log in successfully:

As you can see, we have harvested a default "cool avatar", and prompted the successful login. You're done, a professional login interface that only requires a mobile phone number + verification code + logic, we have completed it completely. We can see that we have mainly lost a bit of effort on the interface adjustment and the introduction of SDK. Actually, the entire logic is implemented. Only need a few lines of code! Because Authing has done a lot of work behind it to ensure the simplicity of the upper logic.

Processing logout logic

In the previous section, we successfully migrated the login logic to the mobile phone number + verification code, and realized the sending of the verification code and the login through a few lines of code.

Because our login logic has some changes compared to before, we have to adjust our logout logic appropriately to adapt to these changes.

Improve logout components

Open the src/components/Logout/index.js file and make corresponding changes to the contents:

import Taro, {useState} from '@ tarojs/taro'
import {AtButton} from 'taro-ui'
import {useDispatch, useSelector} from '@ tarojs/redux'

import {SET_LOGIN_INFO} from '../../constants'
import Authing from '../../utils/authing/authing'

export default function LoginButton(props) {
  const [isLogout, setIsLogout]= useState(false)
  const dispatch = useDispatch()
  const userId = useSelector(state => state.user.userId)

  async function handleLogout() {
    setIsLogout(true)

    try {
      await Taro.removeStorage({key:'userInfo'})
      await Taro.removeStorage({key:'token'})

      const userPoolId = ''
      const authing = new Authing({
        userPoolId,
      })

      await authing.logout(userId)

      dispatch({
        type:SET_LOGIN_INFO,
        payload:{
          avatar:'',
          nickName:'',
          userId:'',
        },
      })
    } catch(err) {
      console.log('removeStorage ERR:', err)
    }

    setIsLogout(false)
  }

  return(
    <AtButton type = "secondary" full loading = {isLogout} onClick = {handleLogout}>
      sign out
    </AtButton>
 )
}

As you can see, the above content has mainly been modified as follows:

  • When we handle the logout logic in the handleLogout function, we first initialized an authing instance. The main thing here is that the userPoolId also needs to be replaced by the reader, which can be obtained in the Authing console, and then call authing .logout Pass in the user's userId to log out this user, so that the user pool created on Authing cannot be operated afterwards
  • Regarding the acquisition of userId, we used the react-redux hook useSelector to obtain it from the Redux Store.
  • Finally, we have to delete the token stored in storage.

Clean up other logout logic

Because our current login is not the previous use of nickName and avatar, but the mobile phone number + verification code, so the default nickName after we log in is empty, and our previous component logic to determine whether the user is logged in It is all to determine whether or not nickName exists, there is a problem here, so we need to modify it.

Open the src/components/Footer/index.js file and make the corresponding changes to the contents as follows:

import Taro from '@ tarojs/taro'
import {View} from '@ tarojs/components'
import {AtFloatLayout} from 'taro-ui'
import {useSelector, useDispatch} from '@ tarojs/redux'

import Logout from '../Logout'
import LoginForm from '../LoginForm'
import './index.scss'
import {SET_IS_OPENED} from '../../constants'

export default function Footer(props) {
  const userId = useSelector(state => state.user.userId)

  const dispatch = useDispatch()

  //Double invert to construct the Boolean value corresponding to the string, used to mark whether the user has logged in at this time
  const isLogged = !! userId

  //Use useSelector Hooks to get Redux Store data
  const isOpened = useSelector(state => state.user.isOpened)

  return(
    <View className = "mine-footer">
      {isLogged && <Logout />}
      <View className = "tuture-motto">
        {isLogged? 'From 图 雀 社区 with Love ❤':'You are not logged in'}
      </View>
      <AtFloatLayout
        isOpened = {isOpened}
        title = "Login"
        onClose = {() =>
          dispatch({type:SET_IS_OPENED, payload:{isOpened:false}})
        }
      >
        <LoginForm />
      </AtFloatLayout>
    </View>
 )
}

As you can see, the above content is mainly to replace nickName with userId and use userId to determine whether you are logged in.

Similarly, src/components/Header/index.js should be similarly modified:

import Taro from '@ tarojs/taro'
import {View} from '@ tarojs/components'
import {AtMessage} from 'taro-ui'
import {useSelector} from '@ tarojs/redux'

import LoggedMine from '../LoggedMine'
import LoginButton from '../LoginButton'
import WeappLoginButton from '../WeappLoginButton'
import AlipayLoginButton from '../AlipayLoginButton'

import './index.scss'

export default function Header(props) {
  const userId = useSelector(state => state.user.userId)

  //Double invert to construct the Boolean value corresponding to the string, used to mark whether the user has logged in at this time
  const isLogged = !! userId

  const isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
  const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY

  return(
    <View className = "user-box">
      <AtMessage />
      <LoggedMine />
      {! isLogged &&(
        <View className = "login-button-box">
          <LoginButton />
          {isWeapp && <WeappLoginButton />}
          {isAlipay && <AlipayLoginButton />}
        </View>
     )}
    </View>
 )
}

And our "my" page, the src/pages/mine/mine.jsx file:

import Taro, {useEffect} from '@ tarojs/taro'
import {View} from '@ tarojs/components'
import {useDispatch, useSelector} from '@ tarojs/redux'

import {Header, Footer} from '../../components'
import './mine.scss'
import {SET_LOGIN_INFO} from '../../constants'

export default function Mine() {
  const dispatch = useDispatch()
  const userId = useSelector(state => state.user.userId)

  const isLogged = !! userId

  useEffect(() => {
    async function getStorage() {
      try {
        const {data} = await Taro.getStorage({key:'userInfo'})

        const {nickName, avatar, _id} = data

        //Update Redux Store data
        dispatch({
          type:SET_LOGIN_INFO,
          payload:{nickName, avatar, userId:_id},
        })
      } catch(err) {
        console.log('getStorage ERR:', err)
      }
    }

    if(! isLogged) {
      getStorage()
    }
  })

  return(
    <View className = "mine">
      <Header />
      <Footer />
    </View>
 )
}

Mine.config = {
  navigationBarTitleText:'mine',
}

Finally, we modify src/pages/index/index.jsx on the homepage:

import Taro, {useEffect} from '@ tarojs/taro'
import {View, Text} from '@ tarojs/components'
import {AtFab, AtFloatLayout, AtMessage} from 'taro-ui'
import {useSelector, useDispatch} from '@ tarojs/redux'

import {PostCard, PostForm} from '../../components'
import './index.scss'
import {
  SET_POST_FORM_IS_OPENED,
  SET_LOGIN_INFO,
  GET_POSTS,
} from '../../constants'

export default function Index() {
  const posts = useSelector(state => state.post.posts) || []
  const isOpened = useSelector(state => state.post.isOpened)
  const userId = useSelector(state => state.user.userId)

  const isLogged = !! userId

  const dispatch = useDispatch()

  useEffect(() => {
    const WeappEnv = Taro.getEnv() === Taro.ENV_TYPE.WEAPP

    if(WeappEnv) {
      Taro.cloud.init()
    }

    async function getStorage() {
      try {
        const {data} = await Taro.getStorage({key:'userInfo'})

        const {nickName, avatar, _id} = data

        //Update Redux Store data
        dispatch({
          type:SET_LOGIN_INFO,
          payload:{nickName, avatar, userId:_id},
        })
      } catch(err) {
        console.log('getStorage ERR:', err)
      }
    }

    if(! isLogged) {
      getStorage()
    }

    async function getPosts() {
      try {
        //Update Redux Store data
        dispatch({
          type:GET_POSTS,
        })
      } catch(err) {
        console.log('getPosts ERR:', err)
      }
    }

    if(! posts.length) {
      getPosts()
    }
  }, [])

  function setIsOpened(isOpened) {
    dispatch({type:SET_POST_FORM_IS_OPENED, payload:{isOpened}})
  }

  function handleClickEdit() {
    if(! isLogged) {
      Taro.atMessage({
        type:'warning',
        message:'You have not logged in yet! ',
      })
    } else {
      setIsOpened(true)
    }
  }

  console.log('posts', posts)

  return(
    <View className = "index">
      <AtMessage />
      {posts.map(post =>(
        <PostCard key = {post._id} postId = {post._id} post = {post} isList />
     ))}
      <AtFloatLayout
        isOpened = {isOpened}
        title = "Post new article"
        onClose = {() => setIsOpened(false)}
      >
        <PostForm />
      </AtFloatLayout>
      <View className = "post-button">
        <AtFab onClick = {handleClickEdit}>
          <Text className = "at-fab__icon at-icon at-icon-edit"> </Text>
        </AtFab>
      </View>
    </View>
 )
}

Index.config = {
  navigationBarTitleText:'Home',
}

After modifying the above code and saving, open the application, we click logout, you should see the following effect smoothly:

Summary

In this section, we echo the logic of logging in using Authing, correspondingly modify the logout logic, and use userId instead of nickName as the criterion for logging in.

Integrated WeChat authorized login

In the first two subsections, we used Authing to integrate the login logic of the mobile phone number + verification code, and then processed the logout logic. Some students may ask, we have replaced the ordinary login, and we have a WeChat login Can it also be replaced with Authing? After all, the integrated user system must be fully integrated, the answer is yes!

Next, we will use the loginWithWxapp provided by Authing to quickly integrate the WeChat authorization login, open the src/components/WeappLoginButton/index.js file, and make the corresponding changes to the content as follows:

import Taro, {useState, useEffect} from '@ tarojs/taro'
import {Button} from '@ tarojs/components'
import {useDispatch} from '@ tarojs/redux'

import './index.scss'
import { SET_LOGIN_INFO } from '../../constants'
import Authing from '../../utils/authing/authing'

export default function WeappLoginButton(props) {
  const [isLogin, setIsLogin]= useState(false)

  const dispatch = useDispatch()

  async function onGetUserInfo(e) {
    setIsLogin(true)

    const userPoolId = '5ea4ffa72b3a80b6eff60b65'
    const authing = new Authing({
      userPoolId,
    })

    async function loginWithAuthing(code, detail) {
      try {
        const userInfo = await authing.loginWithWxapp({
          code,
          detail,
        })

        //提示登录成功
        Taro.atMessage({
          type:'success',
          message:'恭喜您,登录成功!',
        })

        const { nickname, photo, _id } = userInfo

        dispatch({
          type:SET_LOGIN_INFO,
          payload:{ nickName:nickname, avatar:photo, userId:_id },
        })

        //向后端发起登录请求
        await Taro.setStorage({
          key:'userInfo',
          data:{
            nickName:nickname,
            avatar:photo,
            _id,
          },
        })

        await Taro.setStorage({
          key:'token',
          data:userInfo.token,
        })

        //当 code 用于登录之后,会失效,所以这里重新获取 code
        Taro.login({
          success(res) {
            const code = res.code
            Taro.setStorageSync('code', code)
          },
        })
      } catch(err) {
        console.log('err', err)
        Taro.atMessage({
          type:'success',
          message:'恭喜您,登录成功!',
        })
      }
    }

    try {
      const code = Taro.getStorageSync('code')
      Taro.login({
        success(res) {
          const code = res.code
          loginWithAuthing(code, e.detail)
        },
      })
    } catch(err) {
      console.log('err', err)
    }

    setIsLogin(false)
  }

  return(
    <Button
      openType="getUserInfo"
      onGetUserInfo={onGetUserInfo}
      type="primary"
      className="login-button"
      loading={isLogin}
    >
      微信登录
    </Button>
 )
}

可以看到上面的内容主要有如下几处修改:

  • 我们删除了之前简单粗暴获取到 userInfo 里面的 nickNameavatarUrl 就发起登录的代码逻辑。
  • 我们在 onGetUserInfo 里面初始化了一个 authing 实例,然后定义了一个 loginWithAuthing 方法,具体细节我们马上讲解,然后我们使用 Taro.login 调用微信授权登录 API,获取对应的 code ,并连同把 onGetUserInfo 传进来的 e.detail 一起传给 loginWithAuthing
  • loginWithAuthing 函数里面,哦们首先调用 authing.loginWithWxapp ,并传入对应的 codedetail ,进行登录,然后将登录获取的信息存在 storage 里面以及保存在 Redux Store 中,并提示用户登录成功。

保存上面的代码,并运行我们的应用,你应该可以自由的操作微信登录了:

就这样,我们就成功将微信授权登录使用 Authing 集成好了,可以看到我们只需要一个 loginWithWxapp 就把逻辑集成好了,完全不需要之前 dispatch 一个 LOGIN 请求,还要去处理一堆 sagas 逻辑,并且还要编写小程序云函数逻辑,手动处理这些逻辑不仅繁琐,还容易出错,并且也不够灵活,而 Authing 提供的 SDK 可以很好的解决这一点,赋能业务成功。

新版用户系统整合进现有后端

在之前四个小节,我们都在将现有小程序博客的用户逻辑使用 Authing 来替代,而将用户逻辑用 Authing 来替代之后,我们会遇到一个小问题,就是之前的用户系统与其他模块如我们的发帖模块是存在耦合的,所以我们还需要将这个耦合的部分替换成 Authing 的相关逻辑,这就涉及到如何将新版的用户系统整合进现有的后端。

我们目前的博客小程序涉及到和用户系统耦合的部分就是我们云函数 createPost 在发帖的时候要带上用户信息,所以我们需要在这个云函数下使用 Authing 来替换相应的用户逻辑。

安装 SDK

我们的微信小程序后台使用了云函数,而云函数是一个个的 Node.js 函数,而 Authing 为我们提供了 Node.js 的 SDK npm 包,我们马上来安装它,在 functions/createPost 下执行如下的代码:

$npm install authing-js-sdk

执行之后,我们的 package.json 会是如下的样子:

{
  "name":"createPost",
  "version":"1.0.0",
  "description":"",
  "main":"index.js",
  "scripts":{
    "test":"echo \"Error:no test specified\" && exit 1"
  },
  "author":"",
  "license":"ISC",
  "dependencies":{
    "authing-js-sdk":"^3.18.7",
    "wx-server-sdk":"latest"
  }
}

接着,我们在云函数里面替换对应的逻辑,打开 functions/createPost/index.js 文件,对其中的内容做出对应的修改如下:

//云函数入口文件
const cloud = require('wx-server-sdk')
const Authing = require('authing-js-sdk')

cloud.init({
  env:cloud.DYNAMIC_CURRENT_ENV,
})

const db = cloud.database()

//云函数入口函数
exports.main = async(event, context) => {
  const { postData, userId } = event

  const userPoolId = ''
  const secret = ''

  try {
    const authing = new Authing({
      userPoolId,
      secret,
    })
    const userInfo = await authing.user({ id:userId })
    const { nickname, photo } = userInfo

    const { _id } = await db.collection('post').add({
      data:{
        ...postData,
        user:{ nickName:nickname, avatar:photo, _id:userInfo._id },
        createdAt:db.serverDate(),
        updatedAt:db.serverDate(),
      },
    })

    const newPost = await db
      .collection('post')
      .doc(_id)
      .get()

    return {
      post:{ ...newPost.data },
    }
  } catch(err) {
    console.error(`createUser ERR:${err}`)
  }
}

可以看到,我们主要做了如下几处修改:

  • 我们导入了 Authing SDK
  • 然后函数内部我们定义了 userPoolIdsecret 来初始化 authing 实例,这两个参数我们可以在用户池控制台找到:

  • 接着我们使用初始化好的 authing 来调用 authing.user 方法传入我们接收到的 userId 查询在 Authing 中保存的此用户资料,并用这个用户资料替换我们需要在小程序云数据库里面查到的用户数据

自此,我们就在前后端深度整合了 Authing 用户系统,在之后我们的应用扩展过程中,所有和用户有关的逻辑都不需要自己在后台单独编写,前端也大大简化了工作量,并且我们还能在 Authing 的控制台可视化用户的数据:登录情况、登录区域、登录机器,还可以给用户进行权限分配,甚至直接修改用户资料等。

通过鉴权保有用户登录状态

最后,我们来收尾一下,做一下用户登录状态的查询,因为应用的登录凭证它存在一个失效时间,当时间一到,我们再去操作用户信息就会显示没有权限,因为凭证失效了,所以说我们要及时检查用户的登录凭证是否失效,如果失效则要求用户重新登录,这也是读者经常会在访问某些网站的时候遇到,而现在我们将实操一下这个过程。

一般处理用户登录态的验证主要是在应用刚刚启动时,去进行一个鉴权处理,如果用户态有效,则顺利从应用的 storage 里面取出数据,然后设置进前端状态管理,进而展示用户数据,而如果没有则删除 storage 里面的数据,提示用户进行登录。

我们打开 src/pages/index/index.jsx 来实操,对其中的内容作出对应的修改如下:

import Taro, { useEffect } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { AtFab, AtFloatLayout, AtMessage } from 'taro-ui'
import { useSelector, useDispatch } from '@tarojs/redux'

import { PostCard, PostForm } from '../../components'
import './index.scss'
import {
  SET_POST_FORM_IS_OPENED,
  SET_LOGIN_INFO,
  GET_POSTS,
} from '../../constants'
import Authing from '../../utils/authing/authing'

export default function Index() {
  const posts = useSelector(state => state.post.posts) || []
  const isOpened = useSelector(state => state.post.isOpened)
  const userId = useSelector(state => state.user.userId)

  const isLogged = !!userId

  const dispatch = useDispatch()

  useEffect(() => {
    const WeappEnv = Taro.getEnv() === Taro.ENV_TYPE.WEAPP

    if(WeappEnv) {
      Taro.cloud.init()
    }

    async function getStorage() {
      //在应用初始化的时候,对应用进行鉴权,检查登录状态,如果登录失效,则情况缓存信息
      try {
        const userPoolId = ''
        const { data:token } = await Taro.getStorage({ key:'token' })
        const authing = new Authing({
          userPoolId,
        })
        const result = await Taro.request({
          url:`https://users.authing.cn/authing/token?access_token=${userInfo.token}`,
        })

        if(result.data.status) {
          const { data } = await Taro.getStorage({ key:'userInfo' })

          const { nickName, avatar, _id } = data

          //更新 Redux Store 数据
          dispatch({
            type:SET_LOGIN_INFO,
            payload:{ nickName, avatar, userId:_id },
          })
        } else {
          await Taro.removeStorage({ key:'userInfo' })
          await Taro.removeStorage({ key:'token' })
        }
      } catch(err) {
        console.log('getStorage ERR:', err)
      }
    }

    if(!isLogged) {
      getStorage()
    }

    async function getPosts() {
      try {
        //更新 Redux Store 数据
        dispatch({
          type:GET_POSTS,
        })
      } catch(err) {
        console.log('getPosts ERR:', err)
      }
    }

    if(!posts.length) {
      getPosts()
    }
  }, [])

  function setIsOpened(isOpened) {
    dispatch({ type:SET_POST_FORM_IS_OPENED, payload:{ isOpened } })
  }

  function handleClickEdit() {
    if(!isLogged) {
      Taro.atMessage({
        type:'warning',
        message:'您还未登录哦!',
      })
    } else {
      setIsOpened(true)
    }
  }

  console.log('posts', posts)

  return(
    <View className="index">
      <AtMessage />
      {posts.map(post =>(
        <PostCard key={post._id} postId={post._id} post={post} isList />
     ))}
      <AtFloatLayout
        isOpened={isOpened}
        title="发表新文章"
        onClose={() => setIsOpened(false)}
      >
        <PostForm />
      </AtFloatLayout>
      <View className="post-button">
        <AtFab onClick={handleClickEdit}>
          <Text className="at-fab__icon at-icon at-icon-edit"></Text>
        </AtFab>
      </View>
    </View>
 )
}

Index.config = {
  navigationBarTitleText:'首页',
}

可以看到,上面的内容主要做出了如下几处修改:

  • 我们在 getStorage 函数里面,首先获取了之前登录时保存的用户凭证 token ,然后初始化了一个 authing 实例,并通过 Taro.request 的方式,去请求 Authing 为我们提供的鉴权地址:[https://users.authing.cn/authing/token?access_token=YOUR_TOKEN](https://users.authing.cn/authing/token?access_token=YOUR_TOKEN) ,我们将这个链接中的 YOUR_TOKEN 替换成我们保存在 storage 里面的 token ,访问这个地址如果成功则会得到如下的结果:

    {
    "status":true,
    "message":"已登录",
    "code":200,
    "token":{

    "data":{
    "email":"YOUR_EMAIL@domain.com",
    "id":"YOUR_USER_ID",
    "clientId":"YOUR_UESR_POOL_ID"
    },
        "iat":"Token 签发时间"
    "exp":"Token 过期时间"
    }

    }

如果失败其中的 status 会为 false ,其它内容也会相应的变化。

  • 接着我们判断 status ,如果为 true 则从 storage 里面取出数据,设置进 Redux Store,如果为 false ,我们清空 storage 数据,这样在用户发帖时会提示用户需要登录。

summary

通过这篇教程,我们将之前一个比较简单的用户系统替换成了专业的通用云身份提供商 Authing 提供的专业的用户系统,并且体验到了通过短短几行代码就可以实现专业的手机号+验证码登录、用户登出、微信授权登录、并且还可以做用户登录状态的检测等。

有了这样一个简单、方便且强大的用户系统做保障之后,我们的博客应用小程序将可以无顾虑的扩展其它模块,而涉及到身份相关的内容都可以交给 Authing 来做。当然我们这片文章还只用了 Authing 很小的一部分功能,还有诸如企业组织管理、单点登录等高级功能,有兴趣的用户可以自行发掘?!

想要学习更多精彩的实战技术教程?来 图雀社区 逛逛吧。