# Jenkins User Management Architecture: How `User`, `UserProperty`, and `HudsonPrivateSecurityRealm` Work

> Understand Jenkins user management architecture. Learn how User, UserProperty, and HudsonPrivateSecurityRealm store user data and handle authentication for seamless access control.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: architecture
- Published: 2026-07-28

---

**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-in `SecurityRealm` that authenticates against `User` records 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`](https://github.com/jenkinsci/jenkins/blob/main/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`](https://github.com/jenkinsci/jenkins/blob/main/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
// 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`](https://github.com/jenkinsci/jenkins/blob/main/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`](https://github.com/jenkinsci/jenkins/blob/main/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
// 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`](https://github.com/jenkinsci/jenkins/blob/main/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:

```java
// 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.

```groovy
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:

1. The request reaches `SecurityRealm.authenticate2()` implemented by `HudsonPrivateSecurityRealm`.
2. The realm resolves the `User` object and fetches its `Details` property using `User.getProperty(Details.class)`.
3. `Details.isPasswordCorrect()` validates the password hash and returns a Spring `UserDetails` instance.
4. Spring Security creates an `Authentication` object, which Jenkins binds to the HTTP session.
5. Later, permission checks delegate to `User.getACL()`, which is driven by the configured `AuthorizationStrategy`.

## Summary

- **`User`** in [`core/src/main/java/hudson/model/User.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/User.java) is the core identity object. It handles lookup, property management, persistence, and deletion.
- **`UserProperty`** in [`core/src/main/java/hudson/model/UserProperty.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/UserProperty.java) is the extension point for per-user data. Plugins contribute properties via descriptors, and defaults are allocated automatically during user creation.
- **`HudsonPrivateSecurityRealm`** in [`core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java) provides local authentication. It stores password hashes inside the `Details` inner 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`](https://github.com/jenkinsci/jenkins/blob/main/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.