Jenkins User Management Architecture: How `User`, `UserProperty`, and `HudsonPrivateSecurityRealm` Work
Jenkins stores users as hudson.model.User objects, augments each account with a list of UserProperty extensions, and delegates authentication to a SecurityRealm such as HudsonPrivateSecurityRealm.
This article breaks down the Jenkins user management architecture by examining the core classes in the jenkinsci/jenkins repository. You will learn how the user lifecycle is modeled, how plugins attach data to accounts, and how the built-in security realm authenticates locally stored credentials.
The Three Pillars of Jenkins User Management
Jenkins separates identity storage from authentication logic. The design revolves around three key types:
User— The central model object representing an account.UserProperty— An extension point that lets plugins store per-user data.HudsonPrivateSecurityRealm— The built-inSecurityRealmthat authenticates againstUserrecords managed by Jenkins itself.
This separation allows plugins to enrich user profiles without modifying core security code, while the realm only needs a single UserProperty — Details — to validate passwords.
User Lifecycle: Lookup, Creation, and Persistence
The User class in core/src/main/java/hudson/model/User.java manages the entire lifecycle of a Jenkins account.
Loading and Creating a User
To obtain a User instance, code calls User.getById(String id, boolean create). If the user exists in memory or on disk, the method returns the existing object. When create is true and no record is found, Jenkins instantiates a new User, loads or creates a config.xml file, and invokes fixUpAfterLoad() to initialize properties.
Allocating Default Properties
After construction, allocateDefaultPropertyInstancesAsNeeded() iterates over every registered UserPropertyDescriptor via UserPropertyDescriptor.all(). For any descriptor that does not already have a matching instance attached, Jenkins creates a default property and adds it to the user. This mechanism ensures that every account carries the full set of available user properties.
Saving and Deleting
Changes to a user are persisted through User.save(), which serializes the User and its properties to $JENKINS_HOME/users/<hashed-folder>/config.xml. Deletion is handled by User.delete(), which removes the on-disk folder and invalidates the in-memory cache.
// Groovy script example: retrieve or create a user
import hudson.model.User
User alice = User.getById("alice", true)
alice.save()
The UserProperty Extension Mechanism
UserProperty is the core extension point for per-user metadata. The base class lives in core/src/main/java/hudson/model/UserProperty.java, and every concrete implementation pairs with a UserPropertyDescriptor.
Descriptor-Based Discovery
Jenkins discovers properties at startup through the Extension annotation. The static method UserProperty.all() returns every registered descriptor. Each descriptor can create a new property instance via newInstance(User).
Storage and Access
Properties are serialized as child elements inside the <user> node of config.xml. At runtime, you can retrieve a specific property with User.getProperty(Class<T>) or inspect the full list via User.getAllProperties(), which is exposed in the remote API under an admin-only permission check.
// Java plugin example: a custom UserProperty
@Extension
public static final class MyProperty extends UserProperty {
private final String favouriteColour;
@DataBoundConstructor
public MyProperty(String favouriteColour) {
this.favouriteColour = favouriteColour;
}
public String getFavouriteColour() {
return favouriteColour;
}
@Extension
public static class DescriptorImpl extends UserPropertyDescriptor {
@Override
public String getDisplayName() {
return "My custom property";
}
@Override
public MyProperty newInstance(User user) {
return new MyProperty("blue");
}
}
}
When a new User is created, allocateDefaultPropertyInstancesAsNeeded() automatically invokes DescriptorImpl.newInstance(user) and attaches the result.
HudsonPrivateSecurityRealm: Built-In Authentication
When Jenkins manages its own user database, the active SecurityRealm is an instance of HudsonPrivateSecurityRealm in core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java.
The authenticate2 Method
The entry point for login is authenticate2(String username, String password). The realm loads the user's Details property — an inner class that implements UserProperty and wraps a Spring Security UserDetails view — and validates the supplied password:
// In HudsonPrivateSecurityRealm.authenticate2()
Details u = load(username);
if (!u.isPasswordCorrect(password)) {
throw new BadCredentialsException("Bad credentials");
}
return u.asUserDetails();
If the user does not exist, the code performs a dummy password check to mitigate timing attacks before throwing an exception.
The Details Property and Password Hashing
Details stores the password hash in a field named passwordHash. The realm uses MultiPasswordEncoder to choose between BCrypt (default, prefixed with #jbcrypt:) and PBKDF2 (FIPS-140 mode, prefixed with $PBKDF2). The header in the stored hash lets the encoder recognize the algorithm during validation.
Account Creation
Administrators and the setup wizard invoke createAccount(String username, String password) to register new users. This method creates or reuses a User object and attaches a Details property with the hashed password automatically.
import hudson.security.HudsonPrivateSecurityRealm
import hudson.model.User
def realm = Jenkins.instance.getSecurityRealm()
if (realm instanceof HudsonPrivateSecurityRealm) {
User bob = realm.createAccount("bob", "securePassword123")
println "Created user ${bob.id}"
}
UI Integration
The realm conditionally exposes a Manage Users link through an inner class ManageUserLinks. The link is rendered only when HudsonPrivateSecurityRealm is the active security realm, giving administrators a built-in interface to list, edit, or delete accounts.
High-Level Authentication Flow
When a user submits a login request, the following sequence occurs inside the Jenkins user management architecture:
- The request reaches
SecurityRealm.authenticate2()implemented byHudsonPrivateSecurityRealm. - The realm resolves the
Userobject and fetches itsDetailsproperty usingUser.getProperty(Details.class). Details.isPasswordCorrect()validates the password hash and returns a SpringUserDetailsinstance.- Spring Security creates an
Authenticationobject, which Jenkins binds to the HTTP session. - Later, permission checks delegate to
User.getACL(), which is driven by the configuredAuthorizationStrategy.
Summary
Userincore/src/main/java/hudson/model/User.javais the core identity object. It handles lookup, property management, persistence, and deletion.UserPropertyincore/src/main/java/hudson/model/UserProperty.javais the extension point for per-user data. Plugins contribute properties via descriptors, and defaults are allocated automatically during user creation.HudsonPrivateSecurityRealmincore/src/main/java/hudson/security/HudsonPrivateSecurityRealm.javaprovides local authentication. It stores password hashes inside theDetailsinner class, supports BCrypt and PBKDF2, and exposes a management UI.- The architecture cleanly separates identity storage from authentication logic, enabling plugins to extend profiles without touching security code.
Frequently Asked Questions
How does Jenkins store user passwords when using the built-in security realm?
Passwords are stored as hashes inside the Details property attached to each User. The HudsonPrivateSecurityRealm uses MultiPasswordEncoder to generate BCrypt or PBKDF2 hashes, and the hash prefix determines which algorithm is used during validation.
Can plugins add custom fields to a Jenkins user profile?
Yes. Plugins implement the UserProperty extension point and provide a UserPropertyDescriptor. Jenkins automatically instantiates and attaches the property to new users through allocateDefaultPropertyInstancesAsNeeded(). The data is persisted in the user's config.xml.
What happens if HudsonPrivateSecurityRealm is not the active security realm?
When an external realm such as LDAP or SAML is configured, HudsonPrivateSecurityRealm is not installed. User objects may still be created for identity reference, but authentication is delegated to the external provider, and the Manage Users link is hidden.
Is it possible to create a Jenkins user programmatically from a script or plugin?
Yes. If HudsonPrivateSecurityRealm is the active realm, you can call realm.createAccount(username, password). This method creates the User object and attaches the Details property with a hashed password in a single step.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →