I want to set one temporary password for a list of specific users (Not all users, just a specific list of an organization) And this temporary password, the user can change on their next logon.
Is there a way for me to do that performing a Backend Call to Keycloack's Admin API?
I guesss one alternative would be to set this list of users to change password on next logon, can I do that setting a value in a column of a specific Table on KC's Database?
I looked up into Keycloack's Docs and there is the
PUT /admin/realms/{realm}/users/{id}/reset-password
But I think that this method only accepts one user at a time.
You have the ability to change the passwords of users by repeatedly using the PUT API
method.
Each call to the PUT API is dedicated to resetting the password for a single user.
This process can be repeated as needed for multiple users.
You are capable of performing this operation for a large number of users, up to a couple of thousand.
The implication is that the API and your system can handle a significant volume of reset password requests without issues.
The goal is to generate 50 users using a Node.js
script. Each user will have a unique set of credentials like username, password, and email.
For these users, a specific pattern is followed regarding password resetting: Users positioned at odd rows (like the 1st, 3rd, 5th, etc.) will have their passwords marked for a temporary reset. In contrast, users at even rows (2nd, 4th, 6th, etc.) will not require an immediate password reset.
But that order will broken due to sort by username
After generating these users, the next step is to create their profiles in Keycloak, which is an identity and access management service. This involves populating Keycloak with the user data generated by the Node.js script.
Finally, for users who have the reset flag set to true (those in even rows), their passwords will be reset in Keycloak. This action is likely managed through a specific API call to Keycloak for each user needing a password reset.
This process ensures a systematic creation and management of user accounts, with password reset policies applied selectively based on the user's position in the list.
Install Dependencies for node.js
npm install @faker-js/faker axios exceljs qs
Save as save_excel.js
More detail information in here
const { faker } = require('@faker-js/faker');
const ExcelJS = require('exceljs');
const USER_COUNT = 50;
const FILE_NAME = 'Users.xlsx';
// Sets to store used usernames and emails
const usedUsernames = new Set();
const usedEmails = new Set();
// Function to capitalize the first letter of a string
const capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
}
// Function to create a random user with a unique username and email
const createRandomUser = (index) => {
let username, email;
do {
username = faker.internet.userName().toLowerCase();
if (usedUsernames.has(username)) {
username += Math.floor(Math.random() * 10000);
}
} while (usedUsernames.has(username));
usedUsernames.add(username);
do {
email = faker.internet.email().toLowerCase();
if (usedEmails.has(email)) {
email = email.split('@')[0] + Math.floor(Math.random() * 10000) + '@' + email.split('@')[1];
}
} while (usedEmails.has(email));
usedEmails.add(email);
return {
username: username,
password: `${username}_1234`.toLowerCase(),
reset: ((index % 2) ? true : false),
firstName: capitalizeFirstLetter(faker.person.firstName()),
lastName: capitalizeFirstLetter(faker.person.lastName()),
email: email
};
}
// Function to write users to Excel file
const saveUsersToExcel = async (users) => {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Users');
// Define columns
worksheet.columns = [
{ header: 'Username', key: 'username', width: 30 },
{ header: 'Password', key: 'password', width: 30 },
{ header: 'Reset Password', key: 'reset', width: 15 },
{ header: 'First Name', key: 'firstName', width: 20 },
{ header: 'Last Name', key: 'lastName', width: 20 },
{ header: 'Email', key: 'email', width: 50 }
];
// Sort users by username
users.sort((a, b) => a.username.localeCompare(b.username));
// Add users to the worksheet
worksheet.addRows(users);
// Save workbook
await workbook.xlsx.writeFile(FILE_NAME);
console.log(`Saved ${users.length} users to ${FILE_NAME}`);
}
// Main function to create users and save to Excel
const main = async () => {
let users = [];
for (let index = 0; index < USER_COUNT; index++) {
users.push(createRandomUser(index));
}
await saveUsersToExcel(users);
}
main().catch(err => console.error(err));
Run it
node save_excel.js
Result, open "Users.xlsx" file by Excel
"All the username, password, and email fields should be in lowercase, except for the 'First Name' and 'Last Name' fields, where the first letter of each name should be capitalized."
Launching Keycloak by Docker Compose in here
"You will need to increase the duration of the Master Token's validity, which by default is set to 1 minute. Additionally, the realm named 'my-realm' must be created manually."
Save as create_users.js
const axios = require('axios');
const qs = require('qs');
const ExcelJS = require('exceljs');
// Keycloak details
const client_id = 'admin-cli';
const user_name = 'admin';
const pass_word = 'admin';
const grant_type = 'password';
const keycloakUrl = 'http://localhost:8080/auth';
const tokenURL = `${keycloakUrl}/realms/master/protocol/openid-connect/token`;
const my_realm = 'my-realm';
const usersEndpoint = `${keycloakUrl}/admin/realms/${my_realm}/users`;
// Read from Excel file
async function readExcelFile() {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('Users.xlsx');
const worksheet = workbook.getWorksheet('Users');
let users = [];
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
if (rowNumber !== 1) { // Skipping header row
users.push({
username: row.getCell(1).value,
password: row.getCell(2).value,
reset: row.getCell(3).value,
firstName: row.getCell(4).value,
lastName: row.getCell(5).value,
email: row.getCell(6).value
});
}
});
return users;
}
// Function to get token
async function getToken() {
const data = qs.stringify({
client_id: client_id,
username: user_name,
password: pass_word,
grant_type: grant_type
});
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
try {
const response = await axios.post(tokenURL, data, config);
return response.data.access_token;
} catch (error) {
console.error('Error fetching token:', error);
throw error;
}
}
// Function to create user in Keycloak
async function createUserInKeycloak(user, token) {
try {
await axios.post(usersEndpoint, {
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
enabled: true,
emailVerified: true
}, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log(`User created: ${user.username}`);
} catch (error) {
console.error(`Error creating user ${user.username}:`, error);
}
}
async function findUserId(username, token) {
try {
const url = `${keycloakUrl}/admin/realms/${my_realm}/users/?username=${username}`;
const response = await axios.get(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const users = response.data;
if (users && users.length > 0) {
// Assuming the username is unique and the first result is the desired user
return users[0].id;
} else {
console.log(`User not found: ${username}`);
return null;
}
} catch (error) {
console.error(`Error finding user ${username}:`, error);
throw error; // or handle it as needed
}
}
async function setPassword(userId, newPassword, token) {
try {
const url = `${keycloakUrl}/admin/realms/${my_realm}/users/${userId}/reset-password`;
const response = await axios.put(url, {
temporary: false,
type: 'password',
value: newPassword
}, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.status === 204) {
console.log(`Password successfully reset for user ID: ${userId}`);
} else {
console.log(`Received unexpected status code: ${response.status}`);
}
} catch (error) {
console.error(`Error resetting password for user ID ${userId}:`, error);
throw error; // or handle it as needed
}
}
// Main function to handle the process
async function main() {
try {
const users = await readExcelFile();
const token = await getToken();
// create users
for (const user of users) {
await createUserInKeycloak(user, token);
}
// set system default password
for (const user of users) {
const userId = await findUserId(user.username, token)
console.log(`${user.username} ID: ${userId}, password: ${user.password}`);
await setPassword(userId, user.password, token)
}
} catch (error) {
console.error('An error occurred:', error);
}
}
main();
Run it
node create_users.js
Example
But won't reset this user
Save as reset_password_users.js
const axios = require('axios');
const qs = require('qs');
const ExcelJS = require('exceljs');
// Keycloak details
const client_id = 'admin-cli';
const user_name = 'admin';
const pass_word = 'admin';
const grant_type = 'password';
const keycloakUrl = 'http://localhost:8080/auth';
const tokenURL = `${keycloakUrl}/realms/master/protocol/openid-connect/token`;
const my_realm = 'my-realm';
const usersEndpoint = `${keycloakUrl}/admin/realms/${my_realm}/users`;
// Read from Excel file
async function readExcelFile() {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('Users.xlsx');
const worksheet = workbook.getWorksheet('Users');
let users = [];
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
if (rowNumber !== 1) { // Skipping header row
users.push({
username: row.getCell(1).value,
password: row.getCell(2).value,
reset: row.getCell(3).value,
firstName: row.getCell(4).value,
lastName: row.getCell(5).value,
email: row.getCell(6).value
});
}
});
return users;
}
// Function to get token
async function getToken() {
const data = qs.stringify({
client_id: client_id,
username: user_name,
password: pass_word,
grant_type: grant_type
});
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
try {
const response = await axios.post(tokenURL, data, config);
return response.data.access_token;
} catch (error) {
console.error('Error fetching token:', error);
throw error;
}
}
// Function to create user in Keycloak
async function createUserInKeycloak(user, token) {
try {
await axios.post(usersEndpoint, {
username: user.username,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
enabled: true,
emailVerified: true
}, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
console.log(`User created: ${user.username}`);
} catch (error) {
console.error(`Error creating user ${user.username}:`, error);
}
}
async function findUserId(username, token) {
try {
const url = `${keycloakUrl}/admin/realms/${my_realm}/users/?username=${username}`;
const response = await axios.get(url, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
const users = response.data;
if (users && users.length > 0) {
// Assuming the username is unique and the first result is the desired user
return users[0].id;
} else {
console.log(`User not found: ${username}`);
return null;
}
} catch (error) {
console.error(`Error finding user ${username}:`, error);
throw error; // or handle it as needed
}
}
async function resetPassword(userId, newPassword, token) {
try {
const url = `${keycloakUrl}/admin/realms/${my_realm}/users/${userId}/reset-password`;
const response = await axios.put(url, {
temporary: true,
type: 'password',
value: newPassword
}, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.status === 204) {
console.log(`Password successfully reset for user ID: ${userId}`);
} else {
console.log(`Received unexpected status code: ${response.status}`);
}
} catch (error) {
console.error(`Error resetting password for user ID ${userId}:`, error);
throw error; // or handle it as needed
}
}
// Main function to handle the process
async function main() {
try {
const users = await readExcelFile();
const token = await getToken();
// set system default password
for (const user of users) {
if (user.reset) {
const userId = await findUserId(user.username, token)
console.log(`Reset Password User: ${user.username} ID: ${userId}, password: ${user.password}`);
await resetPassword(userId, user.password, token)
}
}
} catch (error) {
console.error('An error occurred:', error);
}
}
main();
Run it
node reset_password_users.js
Result
Try login
username: abagail.parker36
password: abagail.parker36_1234
But 2nd row user no problem to login
username: adelbert8
password: adelbert8_1234