Introduction
As the business ecosystem of a super app develops, more and more third-party services provide a variety of offerings to the app users through mini programs. Each third-party mini program may have its own user system. How can the super app simplify user login and ensure the security of user data? We have developed a login solution tailored for mini programs (or mini games) within the super app, aiming to establish an efficient and secure login system. This allows app users to quickly and seamlessly access various mini programs within the super app ecosystem. Taking WeChat mini program as an example, the login feature typically uses OpenID or UnionID as unique identifiers to integrate with the account system of the mini program service, thereby constructing and designing the user account system.
To address this scenario, you can implement the following solution:
Process
1. The mini program initiates a wx.login request.
2. The SDK obtains a currently logged-in credential (accountid) from the super app, and then requests a temporary code from the OpenServer.
iOS: Implemented through the getAppUID proxy interface. Please refer to the document to set the user ID. Android: Returned by the getAccount proxy interface. Please refer to the document to set the user ID. 3. OpenServer requests the super app backend to verify the user's validity (accountid).
4. A temporary code is generated and returned to the SDK after OpenServer obtains the verification result.
5. The mini program frontend receives the code and sends it to the mini program backend.
6. The mini program backend calls the jscode2Session API to obtain the OpenID and session_key, and manages the association between the user and the OpenID.
Login solution
1. Super app server
The login process requires the super app backend service to provide the following APIs to support the login capabilities.
2. SDK version update
The mini program login feature is supported starting from version 2.1.0. If your SDK version is earlier, please upgrade the version first.
3. Configure the service domain in the console - Application management
To implement unified login of mini programs in your app, you need to first navigate to Console - Application management - Configuration management, and configure the service domain of the app.
4. Integrate the login feature for the mini program
If your mini program is a purely tool-based application and does not need to store any user information on the server, there is no need to integrate the login feature.
However, in most business scenarios, the mini program needs to obtain the user's unique user ID to record various user behavior data in the server-side database. This ensures that when users switch devices or log in again, they can still retrieve their previous data from the server. Typical use cases include: Orders, shopping carts, points, browsing history, etc.
For the above business scenarios, we recommend that mini program developers follow the process below to integrate and implement the feature:
4.1 Generate a key in the console - Mini program management
Each mini program needs to generate a key in Mini program management - Development management - Key management for identity verification when integrating the login feature.
4.2 Mini program frontend
4.2.1 Log in to the mini program
Mini programs can easily obtain the user identity provided by the host app through the standardized login capabilities offered by the platform, thereby quickly establishing a user system within the mini program.
Login process chart
Call wx.login() in the mini program to obtain the temporary login credential code and return data to the mini program backend service. The mini program backend service calls the jscode2Session API in exchange for the user’s unique identifier OpenID and the session key session_key. The mini program backend service can generate a custom login state according to the user identifier, which will be used to identify the user's identity and login state during subsequent interactions between the frontend and backend.
Note:
1. The session key session_key is a key for encrypting and signing user data. For data security of the app, the mini program backend service should not deliver the session key to the mini program, nor should it provide this key to external parties.
2. The temporary login credential code can be used only once.
Example
<button bindtap='login' loading="{{isLoading}}">Authorized login</button>
import Config from './utils/configData';
const app = getApp();
Page({
data: {
isLoading: false,
},
login: function () {
this.setData({
isLoading: true
})
wx.login({
success: (res) => {
console.log('wx.login success===', res)
if (res.code) {
wx.request({
url: `${Config.BASEURL}/getUserInfo`,
method: "POST",
data: {
appid: Config.APPID,
code: res.code
},
success: (res) => {
this.setData({
isLoading: false
})
console.log('wx.request success===', res)
const { code = -1, data = {} } = res?.data || {};
if (code === 200) {
wx.showToast({
title: 'Logged in successfully',
icon: 'success',
duration: 500
})
app.globalData.userInfo = {
avatarUrl: data.avatarUrl,
account: data.account,
nickName: data.userName,
id: data.id,
token: data.token,
phoneNumber: data.phone,
emailAddress: data.email
}
setTimeout(() => {
wx.navigateBack({
delta: 1
})
}, 500)
} else {
const msg = res?.data?.data?.msg || '/getUserInfo request fail'
const errcode = res?.data?.data?.errcode || code
console.log('/getUserInfo request fail', res)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: `/getUserInfo fail:${msg}[code:${errcode}]`,
showCancel: false
})
}
},
fail: (err) => {
this.setData({
isLoading: false
})
console.log('wx.request fail', err)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
},
})
} else {
this.setData({
isLoading: false
})
console.log('wx.login does not return code', res)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: res.errMsg,
showCancel: false
})
}
},
fail: (err) => {
this.setData({
isLoading: false
})
console.log('wx.login fail===', err)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
}
})
}
})
4.2.2 Get the mobile number and email
This feature is designed to help mini program developers request a user's phone number or email address. Developers can only obtain this information with the user's consent, allowing them to provide relevant services to the user.
How to use
Get the mobile number: Set the value of open-type in the button component to getPhoneNumber. After the user permission is provided, use the bindgetphonenumber event to get the callback information. Pass the dynamic token code from the callback information through a custom API (e.g.,/getUserPhone in the example code) to the mini program backend service, and call the getuserphonenumber API provided by the platform’s OpenServer to obtain the user’s mobile number with the code. Get the email address: Set the value of open-type in the button component to getEmailAddress. After the user permission is provided, use the bindgetemailaddress event to get the callback information. Pass the dynamic token code from the callback information through a custom API (e.g.,/getUserEmailDirect in the example code) to the mini program backend service, and call the getemailaddress API provided by the platform’s OpenServer to obtain the user’s email with the code. Note:
1. The codes returned by getPhoneNumber and getEmailAddress are different from the code returned by wx.login.
2. Each code is valid for 5 minutes and can only be used once.
Example
<view>
<button
open-type="getPhoneNumber"
bindtap="clickGetPhoneNumber"
bindgetphonenumber="handleGetPhoneNumber">
Get Phone Number
</button>
<button
open-type="getEmailAddress"
bindtap="clickGetEmailAddress"
bindgetemailaddress="handleGetEmailAddress">
Get Email Address
</button>
</view>
import Config from './utils/configData';
var app = getApp();
Page({
data: {
phoneNumber: '',
emailAddress: '',
},
clickGetEmailAddress() {
wx.showLoading();
},
clickGetPhoneNumber() {
wx.showLoading();
},
handleGetPhoneNumber(e) {
console.log('getPhoneNumber success===', e.detail)
const { code, errMsg } = e.detail
if (code) {
wx.request({
url: `${Config.BASEURL}/getUserPhone`,
method: "POST",
data: {
appid: Config.APPID,
code,
token: app.globalData.userInfo.token
},
success: (res) => {
wx.hideLoading()
console.log('getPhoneNumber request success===', res)
const { code = -1, data = {}, msg } = res?.data || {};
if (code === 200) {
this.setData({
phoneNumber: data?.phoneNumber
})
} else {
const msg = res?.data?.data?.msg || res?.data || '/getPhoneNumber request fail'
const errcode = res?.data?.data?.errcode || code
console.log('/getPhoneNumber request fail', res)
wx.showModal({
title: 'Failed to retrieve phone number',
confirmText: 'Confirm',
content: `/getPhoneNumber fail:${msg}[code:${errcode}]`,
showCancel: false
})
}
},
fail: (err) => {
wx.hideLoading()
console.log('wx.request fail', err)
wx.showModal({
title: 'wx.request fail',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
},
})
} else {
wx.hideLoading()
console.log('getPhoneNumber does not return code', e.detail)
wx.showModal({
title: 'getPhoneNumber fail',
confirmText: 'Confirm',
content: errMsg,
showCancel: false
})
}
},
handleGetEmailAddress(e) {
console.log('getEmailAddress success===', e.detail)
const { code, errMsg } = e.detail
if (code) {
wx.request({
url: `${Config.BASEURL}/getUserEmailDirect`,
method: "POST",
data: {
appid: Config.APPID,
code
},
success: (res) => {
wx.hideLoading()
console.log('getEmailAddress request success===', res)
const { code = -1, data = {}, msg } = res?.data || {};
if (code === 200) {
this.setData({
emailAddress: data?.emailAddress
})
} else {
const msg = res?.data?.data?.msg || res?.data || '/getEmailAddress request fail'
const errcode = res?.data?.data?.errcode || code
console.log('/getEmailAddress request fail', res)
wx.showModal({
title: 'Failed to retrieve email address',
confirmText: 'Confirm',
content: `/getEmailAddress fail:${msg}[code:${errcode}]`,
showCancel: false
})
}
},
fail: (err) => {
wx.hideLoading()
console.log('wx.request fail', err)
wx.showModal({
title: 'wx.request fail',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
},
})
} else {
wx.hideLoading()
console.log('getEmailAddress does not return code', e.detail)
wx.showModal({
title: 'getEmailAddress fail',
confirmText: 'Confirm',
content: errMsg,
showCancel: false
})
}
}
})
4.2.3 Get the profile photo and nickname
When the mini program requests the user to complete the personal data, this can be quickly done using the capabilities provided by the platform.
How to use
Select a profile photo: Set the value of open-type in the button component to chooseAvatar. After the user selects the desired profile photo, the temporary path of profile photo information can be obtained through the callback of the bindchooseavatar event. Enter the nickname: Set the value of type in the input component to nickname. When users type in the input field, the host app's nickname will appear above the keyboard. By tapping the nickname, it will automatically fill the input field. The latest value can be obtained through the bindchange event bindchange event of the input. Note:
In the developer tools, the input component is simulated using a web component, which may not perfectly replicate the behavior on a real device. Therefore, it's recommended that developers debug on an actual device when using native components.
Example
<view class="userInfo-container">
<view class="settingItem">
<text class="caption">Profile Picture</text>
<view class="avatar-empty"></view>
<button class="headerButton" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
<image src="{{userHead}}" class="userHead" mode="aspectFill" />
</button>
</view>
<view class="settingItem">
<text class="caption">Nickname</text>
<input
value="{{nickName}}"
type="nickname"
class="value"
placeholder="Please enter a nickname"
bindchange="nickNameChange" />
</view>
</view>
import Config from './utils/configData';
var app = getApp();
Page({
data: {
userHead: '',
userHeadBase64: '',
nickName: '',
},
onChooseAvatar(e) {
console.log('onChooseAvatar===', e)
const { avatarUrl } = e.detail
if(avatarUrl.includes('/tmp')) {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: avatarUrl,
encoding: 'base64',
success: (data) => {
this.setData({
userHeadBase64: 'data:image/png;base64,' + data.data,
userHead: 'data:image/png;base64,' + data.data
})
},
fail: (err) => {
console.error('readFile error===', err);
}
});
}
if(avatarUrl) {
this.setData({
userHead: avatarUrl,
})
}
},
nickNameChange(e) {
console.log('nickNameChange===', e.detail.value)
this.setData({
nickName: e.detail.value
})
}
})
Page {
width: 100%;
min-height: 100vh;
background: #f2f2f2;
display: flex;
flex-direction: column;
}
.userInfo-container {
display: flex;
flex-direction: column;
}
.settingItem {
display: flex;
width: 100%;
box-sizing: border-box;
flex-direction: row;
background: white;
border-bottom: #dedede solid 1px;
padding: 26rpx;
align-items: center;
}
.avatar-empty {
flex: 1;
}
.headerButton {
padding: 15rpx;
width: 46px !important;
height: 46px !important;
border-radius: 8px;
}
.userHead {
width: 100%;
height: 100%;
pointer-events: none;
}
.settingItem .caption, .settingItem .value {
font-size: 30rpx;
color: #333;
}
.settingItem .value {
flex: 1;
text-align: right;
}
4.3 Mini program server
The mini program backend needs to implement the `${Config.BASEURL}/getUserInfo` API, which is called by the mini program. The purpose of this API is to exchange a code for a unique user identifier and establish the login state of the mini program. The mini program backend needs to call the jscode2Session method provided by OpenServer to obtain a unique OpenID, which can be used to confirm the unique identity of the user. For a specific mini program, the OpenID of each user remains unchanged.
After the backend obtains the OpenID, it should return the necessary user information and a login state session (the expiration time of this session can be customized by the mini program backend). Typically, the OpenID should not be returned to the mini program frontend.
Here is an example of calling jscode2session:
Notice:
Please obtain the calling address from SAS console. For details, please refer to OpenServer. GET https://xxx.xxx.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
In addition, the mini program backend also needs to implement the getAccessToken API. The token has a validity period of 7,200 seconds, and developers need to store it properly. The access_token is usually stored on the backend. The validity period of the access_token is currently 2 hours, and it needs to be refreshed periodically. Repeatedly obtaining the access_token will cause the previously obtained one to become invalid. It is recommended that developers use a central control server to uniformly obtain and refresh the access_token. All other business logic servers should obtain the access_token from this central control server and should not refresh it individually, as this could lead to conflicts and cause the access_token to be overwritten, potentially affecting business operations.
The validity period of the access_token is conveyed through the returned expires_in value, which is currently set to 7,200 seconds. The central control server needs to refresh the token in advance based on this validity period. During the refresh process, the central control server can continue to output the old access_token, and the backend will ensure that both the old and new access_token are valid within 5 minutes, ensuring a smooth transition for third-party services.
The validity period of the access_token may be adjusted in the future. Therefore, the central control server not only needs to proactively refresh the access_token internally at regular intervals but also needs to provide an API for passive refreshing. This allows business servers to trigger the refresh process when they detect that the access_token has expired during API calls.
Benefits
Quick integration: By integrating the super app backend with the product backend, there's no need to develop the mini program login authentication feature. Only a small amount of adaptation work is required.
Unified user data: Mini programs released within the super app share the same login mechanism, ensuring consistency and accuracy of user data between the app and mini programs.
Improved user experience: Super app users can log in to different mini programs using the same credentials, improving user experience and satisfaction.