preface
More and more laptops now have built-in fingerprint recognition for quick access from the lock screen to the desktop, and some client software supports fingerprint authentication.
The other day I was thinking that since the client software can call the fingerprint device, the Web side should also be able to call it. After a lot of work, I finally realized this feature and used it in my open source project.
This article will share with you my implementation ideas and process, welcome interested developers to read this article.
Implementation approach
The browser provides a Web Authentication API that can be used to invoke a user’s fingerprint device to authenticate user information.
The final realization effect video, please step: Web side fingerprint login
Registered fingerprints
First, we need to get the server returns the user credentials, then the user credentials to the fingerprint equipment, tone up the system of fingerprint authentication, certification through after the callback function returns the device id and the client information, we need to save the information on the server, used to call back the fingerprint device to verify user identity, so as to realize the login.
Next, let’s summarize the process of registering fingerprints, as follows:
- After a user successfully logs in to the web site using other methods, the server returns the user credentials and saves them locally
- Check whether a fingerprint device exists on the client
- If so, the user credentials and user information returned by the server are passed to the fingerprint registration function to create the fingerprint
- If the authentication succeeds, the callback function returns the device ID and client information, and saves the device ID to the local computer
- The device ID and client information are sent to the server and stored in the specified user data.
⚠️ Note: Registered fingerprints can only work on websites that use HTTPS connections or localhost.
Fingerprint authentication
User authorization fingerprints after login in our website, user credentials and the device id will be stored in the local, when a user access to our website, will get the the two data from local and suggest whether it need to login system by fingerprint, agreed to after the device id and the user credentials to the fingerprint equipment, tone up the system of fingerprint authentication, certification through, call the login interface, Obtain user information.
Next, let’s summarize the fingerprint authentication process, as follows:
- Obtain the user credentials and device ID from the local device
- Check whether a fingerprint device exists on the client
- If yes, pass the user credentials and device ID to the fingerprint authentication function for verification
- If the authentication succeeds, the login interface is invoked to obtain the user information
⚠️ Note: Fingerprint authentication only works with HTTPS connections, or sites that use localhost.
The implementation process
In the last chapter, we clarified the specific implementation ideas of fingerprint login, and then we will look at the specific implementation process and code.
Server-side implementation
First, we need to write three interfaces on the server: get TouchID, register TouchID, and fingerprint login
Get TouchID
This interface is used to judge whether the login user has registered the fingerprint on this website. If the user has registered, the TouchID will be returned to the client to facilitate the user’s next login.
- The controller layer code is as follows
@apioperation (value = "Get TouchID", notes =" Get fingerprint login credentials from user ID")
@CrossOrigin()
@RequestMapping(value = "/getTouchID", method = RequestMethod.POST)
publicResultVO<? > getTouchID(@apiparam (name = "pass userId", required = true) @Valid @RequestBody GetTouchIdDto touchIdDto, @RequestHeader(value = "token") String token) {
JSONObject result = userService.getTouchID(JwtUtil.getUserId(token));
if (result.getEnum(ResultEnum.class, "code").getCode() == 0) {
// touchId obtained successfully
return ResultVOUtil.success(result.getString("touchId"));
}
// Return an error message
return ResultVOUtil.error(result.getEnum(ResultEnum.class, "code").getCode(), result.getEnum(ResultEnum.class, "code").getMessage());
}
Copy the code
- The implementation code of the interface is as follows
/ / get TouchID
@Override
public JSONObject getTouchID(String userId) {
JSONObject returnResult = new JSONObject();
// Query the touchId from the database based on the current user ID
User user = userMapper.getTouchId(userId);
String touchId = user.getTouchId();
if(touchId ! =null) {
/ / touchId exist
returnResult.put("code", ResultEnum.GET_TOUCHID_SUCCESS);
returnResult.put("touchId", touchId);
return returnResult;
}
// touchId does not exist
returnResult.put("code", ResultEnum.GET_TOUCHID_ERR);
return returnResult;
}
Copy the code
Registered TouchID
This interface is used to receive the TouchID and client information returned by the client fingerprint device and save the obtained information to the specified user in the database.
- The controller layer code is as follows
@apioperation (value = "register TouchID", notes =" Save TouchID returned by client ")
@CrossOrigin()
@RequestMapping(value = "/registeredTouchID", method = RequestMethod.POST)
publicResultVO<? > registeredTouchID(@apiparam (name = "pass userId", required = true) @Valid @RequestBody SetTouchIdDto touchIdDto, @RequestHeader(value = "token") String token) {
JSONObject result = userService.registeredTouchID(touchIdDto.getTouchId(), touchIdDto.getClientDataJson(), JwtUtil.getUserId(token));
if (result.getEnum(ResultEnum.class, "code").getCode() == 0) {
// touchId obtained successfully
return ResultVOUtil.success(result.getString("data"));
}
// Return an error message
return ResultVOUtil.error(result.getEnum(ResultEnum.class, "code").getCode(), result.getEnum(ResultEnum.class, "code").getMessage());
}
Copy the code
- The implementation code of the interface is as follows
/ / register TouchID
@Override
public JSONObject registeredTouchID(String touchId, String clientDataJson, String userId) {
JSONObject result = new JSONObject();
User row = new User();
row.setTouchId(touchId);
row.setClientDataJson(clientDataJson);
row.setUserId(userId);
// Update the touchId and client information based on the userId
int updateResult = userMapper.updateTouchId(row);
if (updateResult>0) {
result.put("code", ResultEnum.SET_TOUCHED_SUCCESS);
result.put("data"."Touch_id set successfully");
return result;
}
result.put("code", ResultEnum.SET_TOUCHED_ERR);
return result;
}
Copy the code
Fingerprint login
This interface receives the user credentials and touchId sent by the client, verifies them against data in the database, and returns the user information.
- The controller layer code is as follows
@apiOperation (value = "Fingerprint login ", notes =" Login system by touchId and user credentials ")
@CrossOrigin()
@RequestMapping(value = "/touchIdLogin", method = RequestMethod.POST)
publicResultVO<? > touchIdLogin(@apiparam (name = "pass in Touch ID and user credentials ", required = true) @Valid @RequestBody TouchIDLoginDto touchIDLogin) {
JSONObject result = userService.touchIdLogin(touchIDLogin.getTouchId(), touchIDLogin.getCertificate());
return LoginUtil.getLoginResult(result);
}
Copy the code
- The implementation code of the interface is as follows
// Fingerprint login
@Override
public JSONObject touchIdLogin(String touchId, String certificate) {
JSONObject returnResult = new JSONObject();
User row = new User();
row.setTouchId(touchId);
row.setUuid(certificate);
User user = userMapper.selectUserForTouchId(row);
String userName = user.getUserName();
String userId = user.getUserId();
// If the user name is null, an error message is returned
if (userName == null) {
// Fingerprint authentication failed
returnResult.put("code", ResultEnum.TOUCHID_LOGIN_ERR);
return returnResult;
}
// If the fingerprint authentication is successful, the user information is returned to the client
/ /... Here the code is omitted, according to their own needs to return user information can be... //
returnResult.put("code", ResultEnum.LOGIN_SUCCESS);
return returnResult;
}
Copy the code
The front-end implementation
In the front end, we need to combine the existing login logic and fingerprint authentication. We need to implement two functions: fingerprint registration and fingerprint login.
The fingerprint register
This function needs to receive three parameters: user name, user ID, and user credentials. We need these three parameters to call the fingerprint device to generate the fingerprint. The specific implementation code is as follows:
touchIDRegistered: async function(
userName: string,
userId: string,
certificate: string
) {
// Verify that the device supports touchID
const hasTouchID = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (
hasTouchID &&
window.confirm("We have detected that your device supports fingerprint login. Do you enable it?")) {// Update the registration certificate
this.touchIDOptions.publicKey.challenge = this.base64ToArrayBuffer(
certificate
);
// Update the user name and id
this.touchIDOptions.publicKey.user.name = userName;
this.touchIDOptions.publicKey.user.displayName = userName;
this.touchIDOptions.publicKey.user.id = this.base64ToArrayBuffer(
userId
);
// Call the fingerprint device to create the fingerprint
const publicKeyCredential = await navigator.credentials.create(
this.touchIDOptions
);
if (publicKeyCredential && "rawId" in publicKeyCredential) {
// Convert rowId to base64
const rawId = publicKeyCredential["rawId"];
const touchId = this.arrayBufferToBase64(rawId);
const response = publicKeyCredential["response"];
// Get the client information
const clientDataJSON = this.arrayBufferToString(
response["clientDataJSON"]);// Call the register TouchID interface
this.$api.touchIdLogingAPI
.registeredTouchID({
touchId: touchId,
clientDataJson: clientDataJSON
})
.then((res: responseDataType<string>) = > {
if (res.code === 0) {
// Save the touchId for fingerprint login
localStorage.setItem("touchId", touchId);
return; } alert(res.msg); }); }}}Copy the code
When creating a fingerprint in the above function, we use an object that must be passed to create a fingerprint. The definition of the object and the explanation of each parameter are as follows:
const touchIDOptions = {
publicKey: {
rp: { name: "chat-system" }, // Website information
user: {
name: ""./ / user name
id: ""./ / user id (ArrayBuffer)
displayName: "" / / user name
},
pubKeyCredParams: [{type: "public-key".alg: -7 // Accept the algorithm}].challenge: ""./ / credentials (touchIDOptions)
authenticatorSelection: {
authenticatorAttachment: "platform"}}}Copy the code
Since some parameters in touchIDOptions need ArrayBuffer type, the data stored in our database is in base64 format, so we need to implement the conversion function between Base64 and ArrayBuffer. The implementation code is as follows:
base64ToArrayBuffer: function(base64: string) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
},
arrayBufferToBase64: function(buffer: ArrayBuffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
Copy the code
After the fingerprint authentication is passed, the client information will be returned in the callback function. The data type is ArrayBuffer, and the format required by the database is string. Therefore, we need to implement the function of transferring ArrayBuffer to String.
arrayBufferToString: function(buffer: ArrayBuffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
Copy the code
Note ⚠️ : The user credentials cannot contain the characters _ and **-**, otherwise the base64ToArrayBuffer function will fail to convert successfully.
Fingerprint login
This function takes two parameters: user credentials and device ID. We will use these two parameters to call the fingerprint device of the client to authenticate the identity. The specific implementation code is as follows:
touchIDLogin: async function(certificate: string, touchId: string) {
// Verify that the device supports touchID
const hasTouchID = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (hasTouchID) {
// Update login credentials
this.touchIDLoginOptions.publicKey.challenge = this.base64ToArrayBuffer(
certificate
);
/ / update the touchID
this.touchIDLoginOptions.publicKey.allowCredentials[0].id = this.base64ToArrayBuffer(
touchId
);
// Start fingerprint verification
await navigator.credentials.get(this.touchIDLoginOptions);
// Invoke the fingerprint login interface
this.$api.touchIdLogingAPI
.touchIdLogin({
touchId: touchId,
certificate: certificate
})
.then((res: responseDataType) = > {
if (res.code == 0) {
// Store the current user information
localStorage.setItem("token", res.data.token);
localStorage.setItem("refreshToken", res.data.refreshToken);
localStorage.setItem("profilePicture", res.data.avatarSrc);
localStorage.setItem("userID", res.data.userID);
localStorage.setItem("username", res.data.username);
const certificate = res.data.certificate;
localStorage.setItem("certificate", certificate);
// Jump the message component
this.$router.push({
name: "message"
});
return;
}
// Switch back to the login page
this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN; alert(res.msg); }); }}Copy the code
Note: ⚠️ : After registering a new fingerprint, the old Touch ID will be invalid, and you can only log in through the new Touch ID. Otherwise, the system cannot adjust the fingerprint device, and an error will be reported: there is a problem with the authentication.
Integrate existing login logic
With the above steps in place, we have implemented the entire fingerprint registration, login logic, let’s look at how to integrate it with existing logins.
Calling fingerprint registration
When the user successfully logs in using the user name, password or third-party platform authorization, we will call the fingerprint registration function to prompt the user whether to authorize the website. The implementation code is as follows:
authLogin: function(state: string, code: string, platform: string) {
this.$api.authLoginAPI
.authorizeLogin({
state: state,
code: code,
platform: platform
})
.then(async (res: responseDataType) => {
if (res.code == 0) {
/ /... Authorized login successful, other code omitted... //
// Save user credentials for fingerprint login
const certificate = res.data.certificate;
localStorage.setItem("certificate", certificate);
// Verify that the device supports touchID
const hasTouchID = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (hasTouchID) {
/ /... Other code omitted... //
// Obtain the Touch ID to check whether the user has authorized the fingerprint login to the website
this.$api.touchIdLogingAPI
.getTouchID({
userId: userId
})
.then(async (res: responseDataType) => {
if(res.code ! = =0) {
// If the touchId does not exist, ask the user to register the touchId
await this.touchIDRegistered(username, userId, certificate);
}
/ / save touchid
localStorage.setItem("touchId", res.data);
// Jump the message component
await this.$router.push({
name: "message"
});
});
return;
}
// The device does not support touchID
await this.$router.push({
name: "message"
});
return;
}
// The login fails and the login page is switched back
this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN;
alert(res.msg);
});
}
Copy the code
The final result looks like this:
Each time a third-party platform authorizes a login, it checks whether the current user is authorized to log in to the website. If the user is authorized, the Touch ID is saved locally for direct login through fingerprint.
Invoke fingerprint login
After the login page is loaded for 1 second, we will take out the user credentials and Touch ID from the local user. If they exist, we will prompt the user whether to log in to the system by fingerprint. The specific code is as follows:
mounted() {
const touchId = localStorage.getItem("touchId");
const certificate = localStorage.getItem("certificate");
// If touchId exists, invoke fingerprint login
if (touchId && certificate) {
// Prompt the user whether to require touchId login
setTimeout(() = > {
if (window.confirm("You have authorized this site to log in with your fingerprint. Do you want to log in now?")) {
this.touchIDLogin(certificate, touchId); }},1000); }}Copy the code
The final result is as follows:
The project address
For the full address of this code, go to login.vue
- Online experience address: Chat-system
- Project GitHub address: Chat-system-Github
Write in the last
I am planning to change my job recently. Is there any company in Guangzhou that can push me to 😁, my wechat: Baymax-kt
- Feel free to correct any mistakes in the comments section, and if this post helped you, feel free to like and follow 😊
- This article was originally published in Nuggets and cannot be reproduced without permission at 💌