
Effective password management is an important aspect of securing user accounts, and Keycloak provides tools to enforce strong authentication policies. By configuring password rules, administrators can ensure that credentials meet security standards, reducing the risk of unauthorized access. The framework offers flexible options, allowing you to set requirements for password length, complexity, expiration, and reuse prevention.
In this blog, we will first take a look at the built-in Keycloak mechanisms for password policy management. Then, we will explore the possibilities for customizing these mechanisms to better fit specific requirements.

Built-in policies
These built-in password policies in Keycloak allow administrators to enforce security rules to strengthen user authentication. Here’s a brief description of each policy:
- Expire Password – Forces users to change their password after a specified period.
- Hashing Iterations – Determines the number of iterations for password hashing to enhance security.
- Not Recently Used – Prevents users from reusing their recent passwords.
- Password Blacklist – Blocks specific passwords from being used, typically to prevent weak or common passwords.
- Regular Expression – Allows enforcing a custom regex pattern for password validation.
- Minimum Length – Sets the minimum number of characters required in a password.
- Not Username – Prevents users from setting their username as a password.
- Not Email – Prevents users from using their email address as a password.
- Not Recently Used (In Days) – Prevents password reuse within a specified number of days.
- Not Contains Username – Ensures the password does not include the username as part of it.
- Special Characters – Requires passwords to contain at least one special character.
- Uppercase Characters – Enforces at least one uppercase letter in the password.
- Lowercase Characters – Requires at least one lowercase letter in the password.
- Digits – Ensures the password includes at least one numeric digit.
- Maximum Authentication Age – Sets a limit on how long authentication remains valid before requiring reauthentication.
- Hashing Algorithm – Specifies the hashing algorithm used for password encryption.
- Maximum Length – Defines the maximum allowable length for passwords.
Implementing custom policy using SPI
To implement a custom password policy in Keycloak, we should use the Service Provider Interface (SPI).
In this case, we define a custom password policy provider by implementing the PasswordPolicyProviderFactory interface:
public class PasswordCustomPolicyProviderFactory implements PasswordPolicyProviderFactory {
public static final Integer DEFAULT_VALUE = 1;
public static final String MIN_PASSWORD_LIFETIME_ID = "minimumPasswordLifetime";
@Override
public String getId() {
return MIN_PASSWORD_LIFETIME_ID;
}
@Override
public PasswordPolicyProvider create(KeycloakSession session) {
return new PasswordCustomPolicyProvider(session);
}
[...]
}
Factory instantiates and returns a new instance of PasswordCustomPolicyProvider, which contains the actual validation logic for enforcing the minimum password lifetime. The MIN_PASSWORD_LIFETIME_ID constant serves as the unique identifier for this custom policy and DEFAULT_VALUE constant represents the default minimum password lifetime (in days) if no custom value is configured via admin console.
public class PasswordCustomPolicyProvider implements PasswordPolicyProvider {
np.
private static final String POLICY_VIOLATION_MESSAGE = "passwordLifetimeViolation";
private final KeycloakSession keycloakSession;
public PasswordCustomPolicyProvider(KeycloakSession keycloakSession) {
this.keycloakSession = keycloakSession;
}
@Override
public PolicyError validate(RealmModel realm, UserModel user, String password) {
PasswordCredentialProvider credentialProvider = new PasswordCredentialProvider(keycloakSession);
PasswordCredentialModel credentialModel = credentialProvider.getPassword(realm, user);
if (credentialModel == null) {
return null;
}
long passwordCreationTime = credentialModel.getCreatedDate();
long currentTime = Time.currentTimeMillis();
long elapsedTime = currentTime - passwordCreationTime;
PasswordPolicy passwordPolicy = realm.getPasswordPolicy();
int minPasswordLifetimeDays = passwordPolicy.getPolicyConfig(PasswordCustomPolicyProviderFactory.MIN_PASSWORD_LIFETIME_ID);
long minPasswordLifetimeMillis = TimeUnit.DAYS.toMillis(minPasswordLifetimeDays);
return elapsedTime >= minPasswordLifetimeMillis ? null : new PolicyError(POLICY_VIOLATION_MESSAGE, minPasswordLifetimeDays);
}
[...]
}
The PasswordCredentialProvider can access the stored password creation timestamp via the PasswordCredentialModel instance. It then computes elapsedTime as the difference between this timestamp and the current system time, representing how long the password has been in use.
Next, the PasswordPolicy object retrieves the password policy for the realm, extracts the minimum required password lifetime in days (minPasswordLifetimeDays), and converts it to milliseconds (minPasswordLifetimeMillis). The policy ensures that the password has been in use for at least the required duration. If this requirement is not met, a PolicyError is returned. The error message key is stored in POLICY_VIOLATION_MESSAGE, and its content can be customized within our theme. This allows us to define a user-friendly message that informs the user why the password change is restricted and how much time remains before a new password can be set.

In this way, we can define custom password policies in Keycloak when the default set of policies is insufficient for specific requirements. This flexibility allows for more granular control over user authentication and password management when we need it.
Customizing UI to improve user experience
By default, Keycloak displays unsatisfied password policies individually on the login page. This can be problematic for many users, especially when there are multiple policies that are not met. It can lead to a cluttered interface and make it harder for users to understand all the password requirements at once. To address this, you can customize the login screen to display a collective list of all unsatisfied password policies together, providing a clearer and more user-friendly experience.
public class CustomFreeMarkerLoginFormsProvider extends FreeMarkerLoginFormsProvider {
/**
* Mapping between password policy provider IDs and custom messages
* Note: contains only standard policies that must be displayed in the UI
*/
private final Map policyPropertyMessages = Map.of(
LengthPasswordPolicyProviderFactory.ID, MINIMUM_LENGTH_MESSAGE,
MaximumLengthPasswordPolicyProviderFactory.ID, MAXIMUM_LENGTH_MESSAGE,
DigitsPasswordPolicyProviderFactory.ID, MINIMUM_DIGIT_MESSAGE,
SpecialCharsPasswordPolicyProviderFactory.ID, MINIMUM_SPECIAL_CHAR_MESSAGE,
UpperCasePasswordPolicyProviderFactory.ID, MINIMUM_UPPERCASE_MESSAGE,
LowerCasePasswordPolicyProviderFactory.ID, MINIMUM_LOWERCASE_MESSAGE,
NotUsernamePasswordPolicyProviderFactory.ID, NOT_USERNAME_MESSAGE,
NotContainsUsernamePasswordPolicyProviderFactory.ID, NOT_CONTAINS_USERNAME_MESSAGE,
NotEmailPasswordPolicyProviderFactory.ID, NOT_EMAIL_MESSAGE
);
[...]
@Override
protected void createCommonAttributes(Theme theme, Locale locale, Properties messagesBundle,
UriBuilder baseUriBuilder, LoginFormsPages page) {
super.createCommonAttributes(theme, locale, messagesBundle, baseUriBuilder, page);
if (realm != null && realm.getPasswordPolicy() != null) {
attributes.put("passwordPolicies", getPasswordPolicyMessages(realm.getPasswordPolicy(), messagesBundle));
}}
[...]
private Map getPasswordPolicyMessages(PasswordPolicy passwordPolicy, Properties messagesBundle) {
Map policyMessages = new HashMap<>();
PasswordPolicy.Builder builder = passwordPolicy.toBuilder();
for (String policyName : passwordPolicy.getPolicies()) {
var value = builder.get(policyName);
String message = extractPolicyMessage(policyName, value, messagesBundle);
if (message != null) {
policyMessages.put(policyName, message);
}
}
return policyMessages;
}
[...]
/**
* Extracts a message for a given password policy from the messages bundle
* Note: Policy message is constructed by replacing the {0} placeholder with the policy value
*/
private String extractPolicyMessage(String policy, String value, Properties messagesBundle) {
String property = policyPropertyMessages.get(policy);
if (property == null) {
return null;
}
String policyMessage = messagesBundle.getProperty(property);
return policyMessage != null ? policyMessage.replace("{0}", value) : null;
}
The getPasswordPolicyMessages() function already collects the password policies from the PasswordPolicy and maps them to the appropriate messages from the message bundle. You can extend this function to display all unsatisfied policies in one collective message.
Password policies such as minimum length, required digits, special characters, etc., are mapped to messages via the extractPolicyMessage() method. Our service implementation will iterate through each policy and check if it’s satisfied. If not, the corresponding message will be displayed.
In your update-password.ftl page, you can display these unsatisfied policies as a list using FreeMarker.
<#if passwordPolicies?has_content>
${msg("passwordInstruction")}
<#list passwordPolicies?keys as key>
• ${passwordPolicies[key]}
#list>
#if>

Real world policy examples
Let’s see how password policies look in large companies.
Apple requires passwords to be at least eight characters long and must include both letters and numbers. Additionally, passwords cannot contain three or more consecutive identical characters and cannot be commonly used passwords.
Facebook enforces a minimum password length of more than six characters, although longer passwords are recommended. While Meta does not require the use of special characters or digits, it encourages creating complex passwords.
Microsoft passwords must be at least 8 characters long and contain at least two of the following types of characters: uppercase letters, lowercase letters, digits, and symbols. Additionally, it may block the ability to set a password that is too similar to the previous one.
Although these companies use different tools for authentication, it’s important to consider the security standards implemented in big real-world systems.
And despite the fact that these password policies are not extremely restrictive, users should still avoid using sensitive personal information, such as names, birthdates, or phone numbers, in their passwords. Additionally, it’s essential to avoid reusing passwords across different services, as this can lead to vulnerabilities in case one account is compromised. Employing two-factor authentication (2FA) and periodically reviewing password security are further steps users can take to enhance their protection.
Summary
As you can see now, Keycloak provides a set of default password policies that cover standard security rules, such as minimum length, complexity requirements, and password history. These built-in policies are sufficient in many cases, but if needed, there is the option to customize them to meet specific organizational requirements. Keycloak also allows the creation of custom password policies, providing greater control over security.
In addition to customizing policies, Keycloak enables modification of the user interface, which is especially useful when the default display of password policy violations, such as showing unmet requirements individually, does not meet our needs. In such cases, we can change how errors are presented or enrich the messages with additional details to make them more user-friendly.
With these options, Keycloak demonstrates a high level of flexibility, allowing full control over security policies and the user interface, making it a versatile solution for identity and access management. The ability to define custom rules and adjust components ensures that Keycloak is a scalable tool that can be easily tailored to the specific needs of an organization.