keycloakreset-password

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.


Solution

  • 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