User's Attribute
can save user email's MD5 hash value.
Also Keycloak API support search by hash value.
User Update API
PUT {Keycloak URL}/admin/realms/{realm}/users/{user-id}
In Body
{
"id": <user id>,
"username": <user name>,
"attributes": { "MD5": [ <user email MD5 hash >] }
}
Search User by attribute
GET {Keycloak URL}/admin/realms/{realm}/users?q={attribute key}:{attribute value}
Example, search by user's MD5 value
GET http://localhost:8080/auth/admin/realms/test/users?q=MD5:3b7c8c7791f4f4c7cdd712635277a1f2
Demo using node.js
const axios = require('axios')
const crypto = require('crypto')
const getMasterToken = async () => {
try {
const response = await axios.post(
url = 'http://localhost:8080/auth/realms/master/protocol/openid-connect/token',
data = new URLSearchParams({
'client_id': 'admin-cli',
'username': 'admin',
'password': 'admin',
'grant_type': 'password'
}),
config = {
headers:
{
'Content-Type': 'application/x-www-form-urlencoded'
}
})
return Promise.resolve(response.data.access_token)
} catch (error) {
return Promise.reject(error)
}
}
const getUser = async (token, username) => {
try {
const response = await axios.get(
url = `http://localhost:8080/auth/admin/realms/test/users?username=${username}`,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
);
return Promise.resolve(response.data[0])
} catch (error) {
return Promise.reject(error)
}
}
const addUserAttribute = async (token, user_data) => {
try {
const MD5 = crypto.createHash('md5').update(`${user_data.email}`).digest("hex")
const newUserData = {
"id": user_data.id,
"username": user_data.username,
"attributes": { "MD5": [MD5] }
}
const response = await axios.put(
url = `http://localhost:8080/auth/admin/realms/test/users/${user_data.id}`,
data = newUserData,
config = {
headers:
{
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
}
})
// response.status = 204 No Content. it means success to update
return Promise.resolve(MD5)
} catch (error) {
return Promise.reject(error)
}
}
const getUserByMD5 = async (token, MD5) => {
try {
const response = await axios.get(
url = `http://localhost:8080/auth/admin/realms/test/users?q=MD5:${MD5}`,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
);
return Promise.resolve(response.data)
} catch (error) {
return Promise.reject(error)
}
}
getMasterToken()
.then((token) => {
getUser(token, 'user2')
.then((user_data) => {
console.log(JSON.stringify(user_data, null, 4))
addUserAttribute(token, user_data)
.then((MD5) => {
console.log(`${user_data.username}'s MD5:` + MD5)
getUserByMD5(token, MD5)
.then((user_update_data) => {
console.log(JSON.stringify(user_update_data, null, 4))
})
})
})
})
.catch(error => console.log(error));
Result
$ node update-user.js
{
"id": "a3831b6a-63e5-471d-b71c-6c7d9f49ee47",
"createdTimestamp": 1677063973333,
"username": "user2",
"enabled": true,
"totp": false,
"emailVerified": false,
"firstName": "Tom",
"lastName": "Cruise",
"email": "user2@gmail.com",
"disableableCredentialTypes": [],
"requiredActions": [],
"notBefore": 0,
"access": {
"manageGroupMembership": true,
"view": true,
"mapRoles": true,
"impersonate": true,
"manage": true
}
}
user2's MD5:fa7c3fcb670a58aa3e90a391ea533c99
[
{
"id": "a3831b6a-63e5-471d-b71c-6c7d9f49ee47",
"createdTimestamp": 1677063973333,
"username": "user2",
"enabled": true,
"totp": false,
"emailVerified": false,
"firstName": "Tom",
"lastName": "Cruise",
"email": "user2@gmail.com",
"attributes": {
"MD5": [
"fa7c3fcb670a58aa3e90a391ea533c99"
]
},
"disableableCredentialTypes": [],
"requiredActions": [],
"notBefore": 0,
"access": {
"manageGroupMembership": true,
"view": true,
"mapRoles": true,
"impersonate": true,
"manage": true
}
}
]
In Keycloak UI

References
Searching for Keycloak user via attribute - searchForUserByUserAttribute - how is it fast?
Keycloak v.18: How to manipulate with users using Keycloak API