Paulo Henrique
Paulo Henrique

Reputation: 13

How to reset the password of a list of users using Keycloack's API?

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.

Upvotes: 1

Views: 1282

Answers (1)

Bench Vue
Bench Vue

Reputation: 9420

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.

Demo steps

enter image description here

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.

Step 1: Create Users save User.xlsx

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."

enter image description here

Step 2: Create Users to Keycloak

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

Result enter image description here

Step 3: Reset Password only true flag users

Example

enter image description here

But won't reset this user

enter image description here

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

enter image description here

Will show ask to new password enter image description here

But 2nd row user no problem to login

username: adelbert8
password: adelbert8_1234

enter image description here

Upvotes: 0

Related Questions