Resetting an Admin Password in Xperience by Kentico (Without SMTP)
Table of Contents
You’re working on a local Xperience by Kentico instance, you go to sign in to /admin, and… you don’t remember the password. The built-in “Forgotten password” link is right there — but it sends a reset email, and your local project has no SMTP server configured.
Xperience by Kentico has no official way to reset a locked-out administrator password without email. So here’s a small, reusable maintenance task that does it: set two environment variables, run the project, and it resets the password and exits.
The solution
Add this block to Program.cs, after app.InitKentico() (so the Kentico data layer is ready) and before app.UseKentico() (so it exits before any middleware or the web server starts):
// Maintenance task: reset an administration user's password, then exit without
// starting the web server. Triggered by environment variables.
var resetUser = Environment.GetEnvironmentVariable("KXP_RESET_ADMIN_USER");
var resetPwd = Environment.GetEnvironmentVariable("KXP_RESET_ADMIN_PWD");
if (!string.IsNullOrWhiteSpace(resetUser) && !string.IsNullOrWhiteSpace(resetPwd))
{
await ResetAdminPasswordAsync(app, resetUser, resetPwd);
return;
}
And the method itself — a top-level local function further down in Program.cs:
static async Task ResetAdminPasswordAsync(WebApplication app, string user, string pwd)
{
using var scope = app.Services.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<AdminUserManager>();
var users = scope.ServiceProvider.GetRequiredService<IInfoProvider<UserInfo>>();
// Admin users live in CMS_User and are managed by AdminUserManager — not the live-site
// UserManager<ApplicationUser> (CMS_Member). Look up the user and reset by unambiguous id.
var info = users.Get()
.WhereEquals(nameof(UserInfo.UserName), user)
.TopN(1)
.FirstOrDefault();
if (info is null)
{
Console.WriteLine($"No CMS_User with UserName '{user}'.");
return;
}
var account = await userManager.FindByIdAsync(info.UserID.ToString());
if (account is null)
{
Console.WriteLine($"Admin identity user not found for id {info.UserID}.");
return;
}
// For an already-registered account, use the reset-token flow (not AddPasswordAsync).
var token = await userManager.GeneratePasswordResetTokenAsync(account);
var result = await userManager.ResetPasswordAsync(account, token, pwd);
Console.WriteLine(result.Succeeded
? $"Password for '{info.UserName}' was reset."
: "Reset failed: " + string.Join("; ", result.Errors.Select(e => e.Description)));
}
The required namespaces are Kentico.Membership (AdminUserManager), CMS.Membership (UserInfo), CMS.DataEngine (IInfoProvider, WhereEquals) and Microsoft.Extensions.DependencyInjection.
Running it
Set the two environment variables and run the project. It resets the password, prints the result, and exits — the web server never starts:
$env:KXP_RESET_ADMIN_USER="administrator"
$env:KXP_RESET_ADMIN_PWD="MyNewPassw0rd!"
dotnet run --no-build
Password for 'administrator' was reset.
It works with any administration username, including email-style ones (jane@example.com). If you get “Reset failed: …” instead, it’s the admin password policy (length/complexity from AdminIdentityOptions) rejecting your choice — the exact validation errors are printed, so pick a stronger password.
Why it’s built this way
Three Xperience by Kentico specifics are baked into that code:
- Admin users aren’t where you’d expect. Everyone who signs in to
/adminlives inCMS_Userand is managed byKentico.Membership.AdminUserManager. The familiarUserManager<ApplicationUser>is for the live site’s members (CMS_Member) and will never find an admin — so injectAdminUserManager. - Use the reset-token flow.
GeneratePasswordResetTokenAsync+ResetPasswordAsyncis the supported way to set the password on an existing account. (AddPasswordAsynconly works for accounts still pending registration.) - Trigger with environment variables, not
--arguments. Xperience runs its own command-line parser duringInitKentico()— that’s what powers--kxp-updateand--kxp-codegen— and it rejects any unknown--optionbefore your code runs. Environment variables sail right past it.
A note on safety
This is a deliberate maintenance back door, so treat it like one:
- It only runs when both environment variables are present — normal startup is unaffected.
- It’s intended for local recovery. In real environments, configure SMTP and use the supported “Forgotten password” flow.
- If you’d rather it can never run in Production, wrap the trigger in
if (app.Environment.IsDevelopment()).
A handy snippet to keep around for the next time you (or a teammate) gets locked out of a local admin. 🔑