diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Custom/LabPasswordRequirements.razor b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Custom/LabPasswordRequirements.razor
new file mode 100644
index 00000000..ae39e1b0
--- /dev/null
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Custom/LabPasswordRequirements.razor
@@ -0,0 +1,98 @@
+@using CodeBeam.UltimateAuth.Credentials.Contracts
+
+@if (Policy is not null)
+{
+
+
+
+
+ @foreach (var rule in Rules)
+ {
+
+
+
+ @rule.Label
+
+
+ }
+
+
+ @if (Policy.CheckCommonPasswords ||
+ Policy.CheckUserSimilarity ||
+ Policy.CheckPasswordHistory)
+ {
+
+ Additional security checks are performed when you submit.
+
+ }
+
+}
+
+@code {
+ [Parameter, EditorRequired]
+ public UAuthPasswordPolicyInfo? Policy { get; set; }
+
+ [Parameter]
+ public string? Password { get; set; }
+
+
+ private IEnumerable<(string Label, bool Passed)> Rules
+ {
+ get
+ {
+ if (Policy is null)
+ yield break;
+
+ var password = Password ?? string.Empty;
+ var hasPassword = password.Length > 0;
+
+ yield return (
+ $"At least {Policy.MinLength} characters",
+ hasPassword && password.Length >= Policy.MinLength);
+
+ yield return (
+ $"At most {Policy.MaxLength} characters",
+ hasPassword && password.Length <= Policy.MaxLength);
+
+ if (Policy.RequireLetter)
+ yield return (
+ "Contains a letter",
+ hasPassword && password.Any(char.IsLetter));
+
+ if (Policy.RequireDigit)
+ yield return (
+ "Contains a number",
+ hasPassword && password.Any(char.IsDigit));
+
+ if (Policy.RequireUppercase)
+ yield return (
+ "Contains an uppercase letter",
+ hasPassword && password.Any(char.IsUpper));
+
+ if (Policy.RequireLowercase)
+ yield return (
+ "Contains a lowercase letter",
+ hasPassword && password.Any(char.IsLower));
+
+ if (Policy.RequireSpecialCharacter)
+ yield return (
+ "Contains a special character",
+ hasPassword && password.Any(c => !char.IsLetterOrDigit(c)));
+ }
+ }
+}
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Custom/LabPasswordRequirements.razor.css b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Custom/LabPasswordRequirements.razor.css
new file mode 100644
index 00000000..a6af4c2e
--- /dev/null
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Custom/LabPasswordRequirements.razor.css
@@ -0,0 +1,50 @@
+.lab-password-policy {
+ padding: 14px 16px;
+ border: 1px solid var(--mud-palette-lines-default);
+ border-radius: 12px;
+ background: var(--mud-palette-background-gray);
+}
+
+.lab-password-policy-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--mud-palette-text-primary);
+}
+
+ .lab-password-policy-header .mud-chip {
+ margin-left: auto;
+ }
+
+.lab-password-policy-rules {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 9px 12px;
+ margin-bottom: 10px;
+}
+
+.lab-password-policy-rule {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ min-width: 0;
+ font-size: 12px;
+ color: var(--mud-palette-text-secondary);
+}
+
+ .lab-password-policy-rule .mud-icon-root {
+ flex-shrink: 0;
+ }
+
+.rule-passed {
+ color: var(--mud-palette-success);
+}
+
+@media (max-width: 480px) {
+ .lab-password-policy-rules {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/MyIdentityPage.razor b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/MyIdentityPage.razor
index 3ee4da90..8c730d92 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/MyIdentityPage.razor
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/MyIdentityPage.razor
@@ -586,7 +586,18 @@
"Change Password",
new DialogParameters
{
- { x => x.PreviewMode, false }
+ { x => x.PreviewMode, false },
+ {
+ x => x.PasswordRequirementsContent,
+ (RenderFragment)(context =>
+ builder =>
+ {
+ builder.OpenComponent(0);
+ builder.AddAttribute(1, "Policy", context.Policy);
+ builder.AddAttribute(2, "Password", context.Password);
+ builder.CloseComponent();
+ })
+ }
},
UAuthDialog.GetDialogOptions(MaxWidth.Small));
}
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/UsersPage.razor b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/UsersPage.razor
index 5c2cecf4..180b806a 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/UsersPage.razor
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/Dashboard/UsersPage.razor
@@ -317,7 +317,18 @@
var parameters = new DialogParameters
{
{ x => x.PreviewMode, false },
- { x => x.OnCreateUserAsync, CreateSandboxUserAsync }
+ { x => x.OnCreateUserAsync, CreateSandboxUserAsync },
+ {
+ x => x.PasswordRequirementsContent,
+ (RenderFragment)(context =>
+ builder =>
+ {
+ builder.OpenComponent(0);
+ builder.AddAttribute(1, "Policy", context.Policy);
+ builder.AddAttribute(2, "Password", context.Password);
+ builder.CloseComponent();
+ })
+ }
};
var dialog = await DialogService.ShowAsync(
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/RegisterPage.razor b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/RegisterPage.razor
index 35140163..d978ccf5 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/RegisterPage.razor
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/Pages/RegisterPage.razor
@@ -1,9 +1,12 @@
@page "/register"
+@using CodeBeam.UltimateAuth.Core.Errors
+
@layout PublicLayout
@implements IDisposable
@inject LabRegistrationService RegistrationService
@inject LabRegistrationAvailabilityService AvailabilityService
+@inject IUAuthClient UAuthClient
@inject ISnackbar Snackbar
@inject NavigationManager Navigation
@@ -122,6 +125,23 @@
autocomplete="new-password"
Validation="@(new Func(ValidatePassword))"
Disabled="_isSubmitting" />
+
+ @if (_passwordPolicy is not null)
+ {
+
+ }
+ else if (_policyLoading)
+ {
+
+ Loading password requirements...
+
+ }
+ else
+ {
+
+ Password requirements will be verified by the server.
+
+ }
PasswordPolicyLimits.AbsoluteMaxLength)
+ return "Password exceeds the maximum allowed length.";
+
+ if (_passwordPolicy is null)
+ return null;
+
+ var violations = UAuthPasswordClientValidator.Validate(value, _passwordPolicy);
+
+ if (violations.Count == 0)
+ return null;
+
+ return violations[0] switch
+ {
+ PasswordPolicyErrorCodes.TooShort =>
+ $"Password must contain at least {_passwordPolicy.MinLength} characters.",
+
+ PasswordPolicyErrorCodes.TooLong =>
+ $"Password cannot exceed {_passwordPolicy.MaxLength} characters.",
+
+ PasswordPolicyErrorCodes.LetterRequired =>
+ "Password must contain a letter.",
+
+ PasswordPolicyErrorCodes.DigitRequired =>
+ "Password must contain a number.",
+
+ PasswordPolicyErrorCodes.UppercaseRequired =>
+ "Password must contain an uppercase letter.",
+
+ PasswordPolicyErrorCodes.LowercaseRequired =>
+ "Password must contain a lowercase letter.",
- return null;
+ PasswordPolicyErrorCodes.SpecialCharacterRequired =>
+ "Password must contain a special character.",
+
+ PasswordPolicyErrorCodes.InputTooLong =>
+ "Password exceeds the maximum allowed length.",
+
+ _ => "Password does not meet the requirements."
+ };
}
private string? ValidateConfirmPassword(string value)
@@ -436,10 +514,7 @@
return Task.CompletedTask;
}
- private async Task CheckUsernameAsync(
- string value,
- int version,
- CancellationTokenSource cts)
+ private async Task CheckUsernameAsync(string value, int version, CancellationTokenSource cts)
{
var token = cts.Token;
@@ -495,10 +570,7 @@
}
}
- private async Task CheckEmailAsync(
- string value,
- int version,
- CancellationTokenSource cts)
+ private async Task CheckEmailAsync(string value, int version, CancellationTokenSource cts)
{
var token = cts.Token;
@@ -567,6 +639,14 @@
if (!_form.IsValid)
return;
+ var passwordError = ValidatePassword(_password);
+
+ if (passwordError is not null)
+ {
+ _errorMessage = passwordError;
+ return;
+ }
+
if (_birthDate is { } birthDate &&
birthDate.Date > DateTime.Today)
{
@@ -630,6 +710,10 @@
_registered = true;
}
+ catch (UAuthValidationException ex)
+ {
+ _errorMessage = LabPasswordErrorMessages.Get(ex.Code);
+ }
catch (Exception)
{
_errorMessage = "An unexpected error occurred. If you already submitted your registration, try signing in before registering again.";
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/_Imports.razor b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/_Imports.razor
index 38cdf158..ecfb50ae 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/_Imports.razor
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Components/_Imports.razor
@@ -25,6 +25,7 @@
@using CodeBeam.UltimateAuth.Client.Blazor
@using CodeBeam.UltimateAuth.Client.Diagnostics
@using CodeBeam.UltimateAuth.Users.Contracts
+@using CodeBeam.UltimateAuth.Credentials.Contracts
@using CodeBeam.UltimateAuth.Authorization.Contracts
@using CodeBeam.UltimateAuth.Sample.Shared.Common
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Contracts/LabPasswordErrorMessages.cs b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Contracts/LabPasswordErrorMessages.cs
new file mode 100644
index 00000000..15f9894a
--- /dev/null
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Contracts/LabPasswordErrorMessages.cs
@@ -0,0 +1,42 @@
+using CodeBeam.UltimateAuth.Credentials.Contracts;
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Contracts;
+
+public static class LabPasswordErrorMessages
+{
+ public static string Get(string? code) => code switch
+ {
+ PasswordPolicyErrorCodes.TooShort =>
+ "Your password is too short.",
+
+ PasswordPolicyErrorCodes.TooLong or
+ PasswordPolicyErrorCodes.InputTooLong =>
+ "Your password exceeds the allowed length.",
+
+ PasswordPolicyErrorCodes.LetterRequired =>
+ "Your password must contain a letter.",
+
+ PasswordPolicyErrorCodes.DigitRequired =>
+ "Your password must contain a number.",
+
+ PasswordPolicyErrorCodes.UppercaseRequired =>
+ "Your password must contain an uppercase letter.",
+
+ PasswordPolicyErrorCodes.LowercaseRequired =>
+ "Your password must contain a lowercase letter.",
+
+ PasswordPolicyErrorCodes.SpecialCharacterRequired =>
+ "Your password must contain a special character.",
+
+ PasswordPolicyErrorCodes.CommonPassword =>
+ "This password is too common. Please choose a less predictable password.",
+
+ PasswordPolicyErrorCodes.SimilarToUser =>
+ "Your password is too similar to your username or email address.",
+
+ PasswordPolicyErrorCodes.ReusedPassword =>
+ "You cannot reuse a previous password.",
+
+ _ => "Your password does not meet the security requirements."
+ };
+}
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261102060817_003_PasswordHistory.Designer.cs b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261102060817_003_PasswordHistory.Designer.cs
new file mode 100644
index 00000000..583c63e0
--- /dev/null
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261102060817_003_PasswordHistory.Designer.cs
@@ -0,0 +1,778 @@
+//
+using System;
+using CodeBeam.UltimateAuth.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Migrations
+{
+ [DbContext(typeof(UAuthDbContext))]
+ [Migration("20261102060817_003_PasswordHistory")]
+ partial class _003_PasswordHistory
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.12")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CredentialType")
+ .HasColumnType("integer");
+
+ b.Property("FailedAttempts")
+ .HasColumnType("integer");
+
+ b.Property("LastFailedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LockedUntil")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RequiresReauthentication")
+ .HasColumnType("boolean");
+
+ b.Property("ResetAttempts")
+ .HasColumnType("integer");
+
+ b.Property("ResetConsumedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ResetExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ResetRequestedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ResetTokenHash")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Scope")
+ .HasColumnType("integer");
+
+ b.Property("SecurityVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "LockedUntil");
+
+ b.HasIndex("Tenant", "ResetRequestedAt");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "Scope");
+
+ b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType")
+ .IsUnique();
+
+ b.ToTable("UAuth_Authentication", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b =>
+ {
+ b.Property("Tenant")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("RoleId")
+ .HasColumnType("uuid");
+
+ b.Property("Permission")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Tenant", "RoleId", "Permission");
+
+ b.HasIndex("Tenant", "Permission");
+
+ b.HasIndex("Tenant", "RoleId");
+
+ b.ToTable("UAuth_RolePermissions", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("NormalizedName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "Id")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "NormalizedName")
+ .IsUnique();
+
+ b.ToTable("UAuth_Roles", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b =>
+ {
+ b.Property("Tenant")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UserKey")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("RoleId")
+ .HasColumnType("uuid");
+
+ b.Property("AssignedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Tenant", "UserKey", "RoleId");
+
+ b.HasIndex("Tenant", "RoleId");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.ToTable("UAuth_UserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastUsedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RevokedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecretHash")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("uuid");
+
+ b.Property("Source")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "ExpiresAt");
+
+ b.HasIndex("Tenant", "Id")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "RevokedAt");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "DeletedAt");
+
+ b.ToTable("UAuth_PasswordCredentials", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordHistoryProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CredentialId")
+ .HasColumnType("uuid");
+
+ b.Property("ReplacedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecretHash")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "CredentialId", "ReplacedAt", "Id");
+
+ b.ToTable("UAuth_PasswordHistory", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AbsoluteExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ActiveSessionId")
+ .HasColumnType("text");
+
+ b.Property("ChainId")
+ .HasColumnType("uuid");
+
+ b.Property("ClaimsSnapshot")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Device")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("DeviceId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RevokedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RootId")
+ .HasColumnType("uuid");
+
+ b.Property("RotationCount")
+ .HasColumnType("integer");
+
+ b.Property("SecurityVersionAtCreation")
+ .HasColumnType("bigint");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("TouchCount")
+ .HasColumnType("integer");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "ChainId")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "RootId");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "DeviceId");
+
+ b.ToTable("UAuth_SessionChains", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChainId")
+ .HasColumnType("uuid");
+
+ b.Property("Claims")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Device")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Metadata")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RevokedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecurityVersionAtCreation")
+ .HasColumnType("bigint");
+
+ b.Property("SessionId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "ChainId");
+
+ b.HasIndex("Tenant", "ExpiresAt");
+
+ b.HasIndex("Tenant", "RevokedAt");
+
+ b.HasIndex("Tenant", "SessionId")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "ChainId", "RevokedAt");
+
+ b.HasIndex("Tenant", "UserKey", "RevokedAt");
+
+ b.ToTable("UAuth_Sessions", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RevokedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("RootId")
+ .HasMaxLength(128)
+ .HasColumnType("uuid");
+
+ b.Property("SecurityVersion")
+ .HasColumnType("bigint");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "RootId")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "RevokedAt");
+
+ b.ToTable("UAuth_SessionRoots", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Tokens.EntityFrameworkCore.RefreshTokenProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChainId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ReplacedByTokenHash")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("RevokedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SessionId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("TokenHash")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("TokenId")
+ .HasColumnType("uuid");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "ChainId");
+
+ b.HasIndex("Tenant", "ExpiresAt");
+
+ b.HasIndex("Tenant", "ReplacedByTokenHash");
+
+ b.HasIndex("Tenant", "SessionId");
+
+ b.HasIndex("Tenant", "TokenHash")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "TokenId");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "ExpiresAt", "RevokedAt");
+
+ b.HasIndex("Tenant", "TokenHash", "RevokedAt");
+
+ b.ToTable("UAuth_RefreshTokens", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserIdentifierProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsPrimary")
+ .HasColumnType("boolean");
+
+ b.Property("NormalizedValue")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Type")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("VerifiedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "NormalizedValue");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "Type", "NormalizedValue")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "UserKey", "IsPrimary");
+
+ b.HasIndex("Tenant", "UserKey", "Type", "IsPrimary", "DeletedAt", "CreatedAt", "Id");
+
+ b.ToTable("UAuth_UserIdentifiers", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserLifecycleProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecurityVersion")
+ .HasColumnType("bigint");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "UserKey")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "DeletedAt", "CreatedAt", "Id");
+
+ b.HasIndex("Tenant", "Status", "DeletedAt", "CreatedAt", "Id");
+
+ b.ToTable("UAuth_UserLifecycles", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Users.EntityFrameworkCore.UserProfileProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Bio")
+ .HasMaxLength(4096)
+ .HasColumnType("character varying(4096)");
+
+ b.Property("BirthDate")
+ .HasColumnType("date");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Culture")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("DeletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DisplayName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("FirstName")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Gender")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Language")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("LastName")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Metadata")
+ .HasColumnType("text");
+
+ b.Property("ProfileKey")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("TimeZone")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "UserKey", "ProfileKey")
+ .IsUnique();
+
+ b.ToTable("UAuth_UserProfiles", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b =>
+ {
+ b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionRootProjection", null)
+ .WithMany()
+ .HasForeignKey("Tenant", "RootId")
+ .HasPrincipalKey("Tenant", "RootId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b =>
+ {
+ b.HasOne("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", null)
+ .WithMany()
+ .HasForeignKey("Tenant", "ChainId")
+ .HasPrincipalKey("Tenant", "ChainId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261102060817_003_PasswordHistory.cs b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261102060817_003_PasswordHistory.cs
new file mode 100644
index 00000000..c00bab6a
--- /dev/null
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261102060817_003_PasswordHistory.cs
@@ -0,0 +1,48 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace CodeBeam.UltimateAuth.IdentityLab.Migrations
+{
+ ///
+ public partial class _003_PasswordHistory : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "UAuth_PasswordHistory",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Tenant = table.Column(type: "character varying(128)", maxLength: 128, nullable: false),
+ UserKey = table.Column(type: "character varying(128)", maxLength: 128, nullable: false),
+ CredentialId = table.Column(type: "uuid", nullable: false),
+ SecretHash = table.Column(type: "character varying(512)", maxLength: 512, nullable: false),
+ ReplacedAt = table.Column(type: "timestamp with time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_UAuth_PasswordHistory", x => x.Id);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_UAuth_PasswordHistory_Tenant_CredentialId_ReplacedAt_Id",
+ table: "UAuth_PasswordHistory",
+ columns: new[] { "Tenant", "CredentialId", "ReplacedAt", "Id" });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_UAuth_PasswordHistory_Tenant_UserKey",
+ table: "UAuth_PasswordHistory",
+ columns: new[] { "Tenant", "UserKey" });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "UAuth_PasswordHistory");
+ }
+ }
+}
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/UAuthDbContextModelSnapshot.cs b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/UAuthDbContextModelSnapshot.cs
index 381996b7..55d8efe3 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/UAuthDbContextModelSnapshot.cs
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/UAuthDbContextModelSnapshot.cs
@@ -249,6 +249,42 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("UAuth_PasswordCredentials", (string)null);
});
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordHistoryProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CredentialId")
+ .HasColumnType("uuid");
+
+ b.Property("ReplacedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SecretHash")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "CredentialId", "ReplacedAt", "Id");
+
+ b.ToTable("UAuth_PasswordHistory", (string)null);
+ });
+
modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b =>
{
b.Property("Id")
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabRegistrationService.cs b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabRegistrationService.cs
index 6e7f4994..94af2313 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabRegistrationService.cs
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabRegistrationService.cs
@@ -1,6 +1,7 @@
using CodeBeam.UltimateAuth.Authorization;
using CodeBeam.UltimateAuth.Core.Defaults;
using CodeBeam.UltimateAuth.Core.Domain;
+using CodeBeam.UltimateAuth.Core.Errors;
using CodeBeam.UltimateAuth.Core.MultiTenancy;
using CodeBeam.UltimateAuth.IdentityLab.Authorization;
using CodeBeam.UltimateAuth.IdentityLab.Contracts;
@@ -27,21 +28,31 @@ public async Task RegisterAsync(LabRegistrationRequest re
var trustedContext = context.WithAttribute(LabProvisioningGate.AttributeKey, LabProvisioningGate.Token);
- var createResult = await userApplicationService.CreateUserAsync(trustedContext,
- new CreateUserRequest
- {
- UserName = request.Username.Trim(),
- Email = request.Email.Trim(),
- Password = request.Password,
- FirstName = string.IsNullOrWhiteSpace(request.FirstName)
- ? null
- : request.FirstName.Trim(),
- LastName = string.IsNullOrWhiteSpace(request.LastName)
- ? null
- : request.LastName.Trim(),
- BirthDate = request.BirthDate
- },
- ct);
+
+ UserCreateResult? createResult; // Burada mevcut metodun gerçek dönüş tipini kullan.
+
+ try
+ {
+ createResult = await userApplicationService.CreateUserAsync(
+ trustedContext,
+ new CreateUserRequest
+ {
+ UserName = request.Username.Trim(),
+ Email = request.Email.Trim(),
+ Password = request.Password,
+ FirstName = string.IsNullOrWhiteSpace(request.FirstName)
+ ? null : request.FirstName.Trim(),
+ LastName = string.IsNullOrWhiteSpace(request.LastName)
+ ? null : request.LastName.Trim(),
+ BirthDate = request.BirthDate
+ },
+ ct);
+ }
+ catch (UAuthValidationException ex)
+ {
+ return LabRegistrationResult.Failed(LabPasswordErrorMessages.Get(ex.Code));
+ }
+
if (!createResult.IsSuccess ||
string.IsNullOrWhiteSpace(createResult.UserKey))
diff --git a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabUserProvisioningService.cs b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabUserProvisioningService.cs
index 858f630f..91f807b1 100644
--- a/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabUserProvisioningService.cs
+++ b/apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Services/LabUserProvisioningService.cs
@@ -20,26 +20,19 @@ public sealed class LabUserProvisioningService(
LabProvisioningRollbackGate rollbackGate,
ILogger logger)
{
- public async Task CreateMemberAsync(
- UserKey actorUserKey,
- CreateUserRequest request,
- CancellationToken ct = default)
+ public async Task CreateMemberAsync(UserKey actorUserKey, CreateUserRequest request, CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(request);
// Obtain the actor from the actual authenticated session.
- var context = await contextProvider.CreateAsync(
- UAuthActions.Users.CreateAdmin,
- "users",
- ct);
+ var context = await contextProvider.CreateAsync(UAuthActions.Users.CreateAdmin, "users", ct);
if (!context.IsAuthenticated ||
context.IsSystemActor ||
context.ActorUserKey is not UserKey authenticatedUserKey ||
authenticatedUserKey != actorUserKey)
{
- throw new UnauthorizedAccessException(
- "Authenticated actor mismatch.");
+ throw new UnauthorizedAccessException("Authenticated actor mismatch.");
}
var sandbox = await sandboxService.GetByUserAsync(
@@ -54,16 +47,12 @@ context.ActorUserKey is not UserKey authenticatedUserKey ||
ct);
if (!access.Allowed)
- throw new UnauthorizedAccessException(
- "Sandbox management permission required.");
+ throw new UnauthorizedAccessException("Sandbox management permission required.");
- var remaining = await sandboxService.GetRemainingCapacityAsync(
- sandbox.Id,
- ct);
+ var remaining = await sandboxService.GetRemainingCapacityAsync(sandbox.Id, ct);
if (remaining <= 0)
- throw new InvalidOperationException(
- "Sandbox user limit reached.");
+ throw new InvalidOperationException("Sandbox user limit reached.");
// Fail before account creation if the required role is absent.
var roleStore = roleStoreFactory.Create(
@@ -76,9 +65,7 @@ context.ActorUserKey is not UserKey authenticatedUserKey ||
"Identity Lab User role is not configured.");
// Only this server-side path can attach the object-identity token.
- var trustedContext = context.WithAttribute(
- LabProvisioningGate.AttributeKey,
- LabProvisioningGate.Token);
+ var trustedContext = context.WithAttribute(LabProvisioningGate.AttributeKey, LabProvisioningGate.Token);
var result = await userApplicationService.CreateUserAsync(trustedContext, request, ct);
diff --git a/docs/content/client-sdk/credentials.md b/docs/content/client-sdk/credentials.md
index 0523b2a4..d97431a1 100644
--- a/docs/content/client-sdk/credentials.md
+++ b/docs/content/client-sdk/credentials.md
@@ -1068,6 +1068,50 @@ The existence of a `CredentialType` enum member does not establish that its comp
Use the currently implemented credential contracts and documented authentication flows.
+## Retrieving Password Requirements
+
+UltimateAuth clients can retrieve the effective password policy configured by the authentication server.
+
+This allows applications to display the current requirements during registration, user creation, and password changes without hardcoding password rules in the UI.
+
+```csharp
+var result = await UAuthClient.Credentials.GetPasswordPolicyAsync();
+
+if (result.IsSuccess && result.Value is { } policy)
+{
+ // Display the effective requirements.
+ var minimumLength = policy.MinLength;
+ var maximumLength = policy.MaxLength;
+ var level = policy.Level;
+}
+```
+
+The response includes the configured policy level, effective length and character requirements, and indicators for server-side common-password, user-similarity, and password-history checks.
+
+### Client-Side Validation
+
+Use `UAuthPasswordClientValidator` to provide immediate feedback before submitting a password:
+
+```csharp
+using CodeBeam.UltimateAuth.Client;
+
+var errors = UAuthPasswordClientValidator.Validate(
+ password,
+ policy);
+
+if (errors.Count == 0)
+{
+ // Local character and length requirements are satisfied.
+ // The server must still validate the submitted password.
+}
+```
+
+The validator returns `PasswordPolicyErrorCodes` values for failed local requirements.
+
+**Client-side validation is advisory, not authoritative.** The server remains responsible for the complete password-policy decision, including common-password detection, user similarity, and password history.
+
+For the built-in Basic, Standard, and Strict requirements, configuration examples, and password-history behavior, see [Password Policy & History](/docs/security/password-policy).
+
## Next Steps
- [Authentication](/docs/client/authentication) — Login, logout, refresh, and authentication flows.
diff --git a/docs/content/getting-started/real-world-setup.md b/docs/content/getting-started/real-world-setup.md
index aa295625..cf851803 100644
--- a/docs/content/getting-started/real-world-setup.md
+++ b/docs/content/getting-started/real-world-setup.md
@@ -264,11 +264,18 @@ builder.Services.AddUltimateAuthServer(options =>
options.Login.LockoutDuration = TimeSpan.FromMinutes(15);
options.Session.Lifetime = TimeSpan.FromDays(7);
options.Identifiers.Behavior.AllowMultipleUsernames = true;
+ options.Password.Level = PasswordPolicyLevel.Strict;
});
```
The effective authentication mode is resolved at runtime. The application does not need to select a single mode globally.
+### Password Security Configuration
+
+UltimateAuth uses the Standard password policy by default. Applications can select Basic or Strict, or override individual requirements through `options.Password`.
+
+For the preset comparison, configuration examples, and password-history settings, see [Password Policy & History](/docs/security/password-policy).
+
## 4. UAuthHub — Dedicated Authentication Server
**Best for:** Applications where multiple clients share one authentication authority.
diff --git a/docs/content/plugin-domains/credentials.md b/docs/content/plugin-domains/credentials.md
index 0858f034..a4dfb6c4 100644
--- a/docs/content/plugin-domains/credentials.md
+++ b/docs/content/plugin-domains/credentials.md
@@ -115,6 +115,10 @@ Secret verification and credential usability are related but distinct decisions.
A correctly supplied password must not make a revoked or otherwise unusable credential valid.
+Password strength, common-password detection, user similarity, and password reuse are governed by configurable security policies.
+
+See [Password Policy & History](/docs/security/password-policy) for the built-in policy levels, defaults, and configuration options.
+
## Credential Validation
UltimateAuth separates credential-specific verification from the broader authentication flow.
diff --git a/docs/content/security/password-policy.md b/docs/content/security/password-policy.md
new file mode 100644
index 00000000..7f42edc6
--- /dev/null
+++ b/docs/content/security/password-policy.md
@@ -0,0 +1,253 @@
+---
+title: Password Policy & History
+order: 6
+group: security
+---
+
+# Password Policy & History
+
+UltimateAuth provides configurable password security policies for applications using its reference password credential implementation.
+
+Instead of requiring every application to implement password validation independently, UltimateAuth provides three built-in policy levels: **Basic**, **Standard**, and **Strict**.
+
+Each level defines a starting set of password requirements. Developers can override individual requirements without replacing the entire policy.
+
+Password validation is enforced on the server. Client applications can retrieve the effective policy to display requirements and provide immediate feedback.
+
+## Password Policy Levels
+
+UltimateAuth includes three built-in presets.
+
+**Standard is the default policy level.**
+
+| Requirement | Basic | Standard (Default) | Strict |
+|---|---|---|---|
+| Minimum length | 8 | 8 | 15 |
+| Maximum length | 128 | 128 | 128 |
+| Require letter | No | Yes | Yes |
+| Require digit | No | Yes | Yes |
+| Require uppercase | No | No | Yes |
+| Require lowercase | No | No | Yes |
+| Require special character | No | No | Yes |
+| Common-password detection | Basic | Standard | Strict |
+| User-similarity detection | Basic | Standard | Strict |
+| Previous passwords remembered | 0 | 0 | 0 |
+
+These values are the built-in defaults, not fixed limits for every application.
+
+### Basic
+
+Basic provides a minimum password length without requiring particular character classes.
+
+It still enables basic common-password and user-similarity checks.
+
+This preset is intended for applications that prefer fewer composition requirements while retaining server-side checks against predictable passwords.
+
+### Standard
+
+Standard is the default UltimateAuth password policy.
+
+It requires at least eight characters, including a letter and a digit, and enables Standard-level common-password and user-similarity checks.
+
+Applications do not need to explicitly select Standard unless they want to make their configuration intentional.
+
+### Strict
+
+Strict increases the minimum length to 15 characters and requires letters, digits, uppercase letters, lowercase letters, and special characters.
+
+It also selects Strict-level common-password and user-similarity checks.
+
+Strict is available for applications that require more restrictive password composition rules. It is not automatically the best policy for every application: usability, password-manager compatibility, account recovery, and the application's threat model should also be considered.
+
+## Configuring Password Policies
+
+Password policies are configured through `UAuthServerOptions.Password`.
+
+### Use a built-in preset
+
+In `Program.cs`:
+
+```csharp
+using CodeBeam.UltimateAuth.Credentials.Contracts;
+
+builder.Services.AddUltimateAuthServer(options =>
+{
+ options.Password.Level = PasswordPolicyLevel.Strict;
+});
+```
+
+This applies the Strict preset, including its default length, character requirements, and optional security checks.
+
+### Customize individual requirements
+
+You can override any supported requirement without creating a custom policy implementation.
+
+```csharp
+using CodeBeam.UltimateAuth.Credentials.Contracts;
+
+builder.Services.AddUltimateAuthServer(options =>
+{
+ options.Password.Level = PasswordPolicyLevel.Standard;
+
+ options.Password.MinLength = 12;
+ options.Password.MaxLength = 64;
+
+ options.Password.RequireUppercase = true;
+ options.Password.RequireSpecialCharacter = true;
+});
+```
+
+The effective policy starts with Standard and applies the explicitly configured overrides.
+
+Unspecified requirements retain their preset values.
+
+For example, this configuration still requires a letter and a digit, even though those properties are not explicitly set.
+
+### Disable an individual security check
+
+Common-password detection and user-similarity detection are configured independently from the main preset.
+
+```csharp
+builder.Services.AddUltimateAuthServer(options =>
+{
+ options.Password.Level = PasswordPolicyLevel.Standard;
+
+ options.Password.CommonPasswords =
+ PasswordPolicyLevel.Strict;
+
+ options.Password.UserSimilarity =
+ PasswordPolicyLevel.Disabled;
+});
+```
+
+This keeps Standard password composition rules, increases common-password detection to Strict, and disables user-similarity detection.
+
+Disabling security checks should be a deliberate application decision.
+
+## Password Length Limits
+
+UltimateAuth defines the following limits:
+
+| Setting | Value |
+|---|---|
+| Minimum configurable password length | 6 |
+| Default maximum password length | 128 |
+| Absolute input length limit | 256 |
+
+The built-in presets use a maximum length of 128 characters.
+
+Applications can customize the configured maximum within the supported limits. The absolute limit is a defensive boundary and is not intended to replace application-level password policy configuration.
+
+Invalid password policy options are rejected during server options validation.
+
+## Common-Password Detection
+
+A password may satisfy every character requirement and still be easy to guess.
+
+For example, a password such as `qwerty123` may satisfy Standard's letter, digit, and length requirements but still be rejected by the server's common-password checker.
+
+UltimateAuth supports independently configurable detection levels:
+
+- `Disabled`
+- `Basic`
+- `Standard`
+- `Strict`
+
+The selected level determines the checker configuration used by the reference implementation.
+
+Common-password detection is a server-side operation. A client-side character validator cannot establish whether a password passes this check.
+
+## User-Similarity Detection
+
+Passwords that closely resemble account identifiers or other available user information may be predictable.
+
+UltimateAuth provides user-similarity checking with the same configurable levels:
+
+- `Disabled`
+- `Basic`
+- `Standard`
+- `Strict`
+
+The server evaluates similarity using the available password-policy context.
+
+Client applications should not attempt to reproduce the complete similarity decision from public policy information alone.
+
+## Password History
+
+Password history helps prevent users from repeatedly selecting passwords they have used before.
+
+UltimateAuth can remember a configurable number of previous passwords and reject reuse during applicable password credential operations.
+
+### Enable password history
+
+```csharp
+builder.Services.AddUltimateAuthServer(options =>
+{
+ options.Password.History.RememberLast = 5;
+});
+```
+
+`RememberLast` controls how many previous passwords are checked.
+
+| Value | Behavior |
+|---|---|
+| `0` | Previous-password history checking is disabled |
+| `1–10` | Check up to the configured number of previous passwords |
+
+The default value is `0`.
+
+The reference password-history validator also checks whether the submitted password matches the current credential, independently of the configured previous-password count.
+
+Password history compares password candidates against protected password representations; it does not require retaining previous passwords in plaintext.
+
+### Persistence
+
+Password history requires an appropriate history store.
+
+UltimateAuth provides reference storage implementations for in-memory and Entity Framework Core persistence.
+
+For persistent deployments, apply the required database migrations before enabling password-history functionality.
+
+An in-memory store is useful for development and testing but does not provide durable history across application restarts.
+
+## Server Enforcement and Client Feedback
+
+UltimateAuth distinguishes between authoritative password validation and client-side guidance.
+
+**The server is the security boundary.**
+
+Client applications may retrieve the effective policy and validate its publicly expressible requirements, including:
+
+- Minimum and maximum length
+- Letter and digit requirements
+- Uppercase and lowercase requirements
+- Special-character requirements
+
+Other checks remain server-authoritative:
+
+- Common-password detection
+- User-similarity detection
+- Password history
+
+A password passing client-side validation is not guaranteed to be accepted by the server.
+
+Applications should handle server-side password-policy violations as expected validation errors, rather than displaying them as unexpected application failures.
+
+For client API usage and examples, see [Client SDK — Credentials](/docs/client/credentials).
+
+## Security Considerations
+
+Password policies are one part of credential security, not a replacement for the broader authentication system.
+
+Applications should also consider secure password hashing, transport security, login throttling, account recovery, session revocation, and appropriate authorization for credential-management operations.
+
+Changing a password may also affect existing authentication sessions, depending on the configured credential session policy.
+
+See [Session Security Model](/docs/security/session-security) for the relevant session security boundaries.
+
+## Next Steps
+
+- [Credentials Domain](/docs/plugin-domains/credentials) — Understand credential ownership, validation, and lifecycle.
+- [Client SDK — Credentials](/docs/client/credentials) — Retrieve password requirements and implement credential-management flows.
+- [Real-World Setup](/docs/getting-started/real-world-setup) — Configure UltimateAuth in a real application.
+- [Session Security Model](/docs/security/session-security) — Understand the security consequences of credential changes.
diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json
index 1a9590e6..771a8890 100644
--- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json
+++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/client-sdk/credentials.json
@@ -1,7 +1,7 @@
{
"Slug": "client-sdk/credentials",
"Title": "Credentials",
- "Html": "\n\u003Cp\u003EUltimateAuth provides a dedicated client API for managing authentication credentials, including password creation, password changes, credential revocation, account recovery, and administrative credential operations.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Credentials\u003C/code\u003E, applications can implement credential management without manually constructing requests to the underlying endpoints.\u003C/p\u003E\n\u003Cp\u003ECredential management is security-sensitive. UltimateAuth separates authenticated credential changes from recovery workflows and provides configurable session invalidation policies.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EICredentialClient\u003C/code\u003E API, its request and response models, and practical implementation patterns.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003E1. Understanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential is authentication material associated with a user.\u003C/p\u003E\n\u003Cp\u003EPasswords are the primary credential type supported by the current credential management request contracts.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth also defines a broader \u003Ccode\u003ECredentialType\u003C/code\u003E enum for additional authentication mechanisms.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic enum CredentialType\n{\n Password = 0,\n OneTimeCode = 10,\n EmailOtp = 11,\n SmsOtp = 12,\n Totp = 30,\n Passkey = 100,\n Certificate = 200,\n ApiKey = 210,\n External = 1000\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese enum values do not mean every credential type is currently supported by the credential management API.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EAddCredentialRequest\u003C/code\u003E currently accepts a secret but does not expose a credential type selector.\u003C/p\u003E\n\u003Cp\u003EDo not assume that passkeys, API keys, or external authentication credentials can be created through \u003Ccode\u003EAddMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-credential-client\u0022\u003E2. Accessing the Credential Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into your Blazor component:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential operations are available through:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthClient.Credentials\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003ECredentials\u003C/code\u003E property exposes \u003Ccode\u003EICredentialClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts operations fall into three groups:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGroup\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelf-service\u003C/td\u003E\n\u003Ctd\u003EAdd, change, and revoke the current user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERecovery\u003C/td\u003E\n\u003Ctd\u003EBegin and complete a credential reset\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EManage another user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ERecovery operations are designed to support users who cannot authenticate normally.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-your-password\u0022\u003E3. Change Your Password\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeMyAsync\u003C/code\u003E when an authenticated user knows their current password and wants to replace it.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password changed successfully.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialChangeResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changecredentialrequest\u0022\u003EChangeCredentialRequest\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EType\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent password; required for self-service changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew password\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is nullable in the shared request contract because administrative changes do not require the user\u0027s existing password.\u003C/p\u003E\n\u003Cp\u003EFor self-service password changes, provide the current secret.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialchangeresult\u0022\u003ECredentialChangeResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the credential change succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional operation-level failure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type affected by the change\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to change your password.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } change)\n{\n errorMessage = change.Error ?? \u0026quot;Password change failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been changed.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECheck both the API-level result and the operation-level result when processing credential changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-state-event\u0022\u003EClient State Event\u003C/h3\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003EChangeMyAsync\u003C/code\u003E operation, the SDK publishes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthStateEvent.CredentialsChangedSelf\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event uses the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can help the application refresh authentication-related UI.\u003C/p\u003E\n\u003Cp\u003EIt does not replace the server\u0027s credential or session security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-password-change-form\u0022\u003E4. Build a Password Change Form\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates an authenticated password change form in Blazor.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Change Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;EditForm Model=\u0026quot;_model\u0026quot; OnValidSubmit=\u0026quot;ChangePasswordAsync\u0026quot;\u0026gt;\n \u0026lt;DataAnnotationsValidator /\u0026gt;\n\n \u0026lt;label\u0026gt;Current password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.CurrentPassword\u0026quot; /\u0026gt;\n\n \u0026lt;label\u0026gt;New password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.NewPassword\u0026quot; /\u0026gt;\n\n \u0026lt;button type=\u0026quot;submit\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Change password\u0026lt;/button\u0026gt;\n\u0026lt;/EditForm\u0026gt;\n\n@if (!string.IsNullOrWhiteSpace(_message))\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private readonly PasswordChangeModel _model = new();\n private bool _busy;\n private string? _message;\n\n private async Task ChangePasswordAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = _model.CurrentPassword,\n NewSecret = _model.NewPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp;\n result.Value is { IsSuccess: true })\n {\n _model.CurrentPassword = \u0026quot;\u0026quot;;\n _model.NewPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password changed successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to change password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The credential service is unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n\n private sealed class PasswordChangeModel\n {\n [System.ComponentModel.DataAnnotations.Required]\n public string CurrentPassword { get; set; } = \u0026quot;\u0026quot;;\n\n [System.ComponentModel.DataAnnotations.Required]\n public string NewPassword { get; set; } = \u0026quot;\u0026quot;;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the client integration.\u003C/p\u003E\n\u003Cp\u003EA production form should additionally provide password confirmation, appropriate password policy feedback, and safeguards against repeated submissions.\u003C/p\u003E\n\u003Cp\u003ENever log password values or include them in application telemetry.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential\u0022\u003E5. Add a Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddMyAsync\u003C/code\u003E to add a credential to the current user.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.AddMyAsync(\n new AddCredentialRequest\n {\n Secret = password\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine($\u0026quot;Credential ID: {added.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialAddResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022addcredentialrequest\u0022\u003EAddCredentialRequest\u003C/h3\u003E\n\u003Cp\u003EThe current request contract is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record AddCredentialRequest\n{\n public required string Secret { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential type and source selection are not currently exposed by this request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialaddresult\u0022\u003ECredentialAddResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the credential was added\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional failure information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential ID, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential type, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdding a credential is subject to the server\u0027s credential policies.\u003C/p\u003E\n\u003Cp\u003EFor example, the server may reject attempts to create a duplicate or otherwise unsupported credential.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-change-versus-password-recovery\u0022\u003E6. Password Change Versus Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between two different workflows.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EWorkflow\u003C/th\u003E\n\u003Cth\u003ETypical situation\u003C/th\u003E\n\u003Cth\u003EAPI\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword change\u003C/td\u003E\n\u003Ctd\u003EAuthenticated user knows the current password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword recovery\u003C/td\u003E\n\u003Ctd\u003EUser cannot authenticate using the existing password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E \u2192 \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA password change verifies the existing secret.\u003C/p\u003E\n\u003Cp\u003EPassword recovery uses a separate proof mechanism, such as a reset token or code delivered through a trusted channel.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not use administrative password changes as a replacement for public password recovery.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-password-recovery\u0022\u003E7. Begin Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EBeginResetAsync\u003C/code\u003E to initiate a credential recovery workflow.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } reset)\n{\n Console.WriteLine($\u0026quot;Expires at: {reset.ExpiresAt}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialBeginResetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginresetcredentialrequest\u0022\u003EBeginResetCredentialRequest\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser identifier associated with the recovery request\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type to recover\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCodeType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReset proof format\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reset-code-types\u0022\u003EReset Code Types\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic enum ResetCodeType\n{\n Token = 0,\n Code = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EToken\u003C/code\u003E for a token-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECode\u003C/code\u003E for a code-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EThe delivery mechanism is determined by the application\u0027s configured server-side notification implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialbeginresetresult\u0022\u003ECredentialBeginResetResult\u003C/h3\u003E\n\u003Cp\u003EThe result contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record CredentialBeginResetResult\n{\n public DateTimeOffset ExpiresAt { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe reset token or code is not returned in the API response.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis prevents the recovery endpoint from directly exposing the secret needed to take over an account.\u003C/p\u003E\n\u003Cp\u003EThe proof must be delivered through the configured trusted notification channel.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-password-recovery\u0022\u003E8. Complete Password Recovery\u003C/h2\u003E\n\u003Cp\u003EAfter the user receives a reset token or code, call \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetToken = resetToken,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password recovery completed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialActionResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completeresetcredentialrequest\u0022\u003ECompleteResetCredentialRequest\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier used for the recovery workflow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type being recovered\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetToken\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDelivered recovery proof\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReplacement secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe property is named \u003Ccode\u003EResetToken\u003C/code\u003E even when the selected reset format is a code.\u003C/p\u003E\n\u003Cp\u003EUse the exact contract property name.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialactionresult\u0022\u003ECredentialActionResult\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record CredentialActionResult\n{\n public bool IsSuccess { get; init; }\n public string? Error { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to complete password recovery.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n errorMessage = action.Error ?? \u0026quot;Password recovery failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been reset.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E operation, the SDK publishes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event is not the same as \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E, which is published by \u003Ccode\u003EChangeMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-recovery-using-a-numeric-code\u0022\u003E9. Complete Recovery Using a Numeric Code\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports code-based recovery through \u003Ccode\u003EResetCodeType.Code\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EBegin the recovery:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar begin = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Code\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen submit the code received by the user:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar complete = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetToken = enteredCode,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same completion API handles token-based and code-based recovery.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for validating the submitted proof against the active recovery state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-forgot-password-form\u0022\u003E10. Build a Forgot Password Form\u003C/h2\u003E\n\u003Cp\u003EA typical forgot-password interface has two stages:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ERequest a recovery token or code.\u003C/li\u003E\n\u003Cli\u003ESubmit the received proof with a new password.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-one-request-recovery\u0022\u003EStep One: Request Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Forgot Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; type=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;BeginRecoveryAsync\u0026quot;\u0026gt;Send recovery instructions\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string? _message;\n private bool _busy;\n\n private async Task BeginRecoveryAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_email))\n return;\n\n _busy = true;\n\n try\n {\n var result = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\n _message = result.IsSuccess\n ? \u0026quot;If the account is eligible, recovery instructions will be sent.\u0026quot;\n : \u0026quot;Unable to process the recovery request.\u0026quot;;\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe confirmation message intentionally avoids revealing whether an account exists.\u003C/p\u003E\n\u003Cp\u003EApplications should avoid introducing account enumeration through UI messages, timing differences, or diagnostic details.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-two-complete-recovery\u0022\u003EStep Two: Complete Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Reset Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_token\u0026quot; placeholder=\u0026quot;Recovery token or code\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newPassword\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;New password\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;CompleteRecoveryAsync\u0026quot;\u0026gt;Reset password\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string _token = \u0026quot;\u0026quot;;\n private string _newPassword = \u0026quot;\u0026quot;;\n\n private string? _message;\n private bool _busy;\n\n private async Task CompleteRecoveryAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetToken = _token,\n NewSecret = _newPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n {\n _token = \u0026quot;\u0026quot;;\n _newPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password reset successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to reset password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese examples show the SDK integration.\u003C/p\u003E\n\u003Cp\u003EA production implementation should also validate user input, protect recovery proofs from accidental disclosure, and avoid logging reset tokens or passwords.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-reset-notifications\u0022\u003E11. Configure Reset Notifications\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003EICredentialResetNotifier\u003C/code\u003E to deliver recovery tokens and codes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic interface ICredentialResetNotifier\n{\n Task NotifyAsync(CredentialResetNotification notification, CancellationToken ct = default);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe notification includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record CredentialResetNotification(\n TenantKey Tenant,\n UserKey UserKey,\n CredentialType CredentialType,\n ResetCodeType CodeType,\n string Token,\n DateTimeOffset ExpiresAt);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application must provide a secure delivery implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-notifier\u0022\u003ERegister the Notifier\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationCredentialResetNotifier\u003C/code\u003E is an application-defined implementation.\u003C/p\u003E\n\u003Cp\u003EIt might deliver the proof through email, SMS, or another trusted channel associated with the user\u0027s account.\u003C/p\u003E\n\u003Cp\u003EThe notifier should not expose the proof to an arbitrary address supplied by an unauthenticated caller.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-notifier-configuration\u0022\u003EMissing Notifier Configuration\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a \u003Ccode\u003ENotConfiguredCredentialResetNotifier\u003C/code\u003E fallback.\u003C/p\u003E\n\u003Cp\u003EWhen recovery requires notification but no implementation has been configured, the fallback throws an explanatory exception.\u003C/p\u003E\n\u003Cp\u003EThis prevents a reset flow from silently succeeding without a configured proof delivery mechanism.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever return recovery tokens to anonymous clients as a substitute for configuring the notifier.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-security-behavior\u0022\u003E12. Recovery Security Behavior\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is designed around several security properties.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-enumeration-resistance\u0022\u003EAccount Enumeration Resistance\u003C/h3\u003E\n\u003Cp\u003ERecovery initiation should not reveal whether a submitted identifier belongs to an account.\u003C/p\u003E\n\u003Cp\u003EApplications should preserve this behavior in their own UI and logging policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022proof-expiration\u0022\u003EProof Expiration\u003C/h3\u003E\n\u003Cp\u003ERecovery proofs have a server-controlled lifetime.\u003C/p\u003E\n\u003Cp\u003EThe client does not choose an arbitrary expiration time.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-use-proofs\u0022\u003ESingle-Use Proofs\u003C/h3\u003E\n\u003Cp\u003EA successfully consumed recovery proof must not be reusable.\u003C/p\u003E\n\u003Cp\u003EAfter successful completion, a second attempt using the same proof should be rejected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-and-target-binding\u0022\u003EIdentifier and Target Binding\u003C/h3\u003E\n\u003Cp\u003ERecovery must apply to the correct user and credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative recovery must not permit an identifier belonging to one user to reset another user\u0027s credential.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h3\u003E\n\u003Cp\u003ESuccessful recovery may invalidate existing sessions according to the configured credential session policy.\u003C/p\u003E\n\u003Cp\u003EBy default, UltimateAuth revokes all session chains after recovery.\u003C/p\u003E\n\u003Cp\u003EFor further details, see \u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-your-credential\u0022\u003E13. Revoke Your Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E to revoke a credential belonging to the authenticated user.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.RevokeMyAsync(\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\nif (result.IsSuccess)\n{\n // Credential revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contract is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record RevokeCredentialRequest\n{\n public Guid Id { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the credential record ID, not a user ID or session ID.\u003C/p\u003E\n\u003Cp\u003ECredential revocation is distinct from credential deletion.\u003C/p\u003E\n\u003Cp\u003ERevocation changes the credential\u0027s usability, while deletion uses a separate lifecycle operation available through the administrative API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-event\u0022\u003EState Event\u003C/h3\u003E\n\u003Cp\u003EAfter successful \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E, the SDK publishes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application can use this event to refresh credential-related UI.\u003C/p\u003E\n\u003Cp\u003ECredential revocation may affect authentication eligibility and session continuation depending on server policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-credential-management\u0022\u003E14. Administrative Credential Management\u003C/h2\u003E\n\u003Cp\u003EAdministrative credential operations target a specific user.\u003C/p\u003E\n\u003Cp\u003EThey require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential-1\u0022\u003EAdd a Credential\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.AddUserAsync(userKey,\n new AddCredentialRequest\n {\n Secret = temporaryPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server validates whether the administrative actor may add a credential for the target user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-a-users-password\u0022\u003EChange a User\u0027s Password\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.ChangeUserAsync(userKey,\n new ChangeCredentialRequest\n {\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Administrative password change succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is not required for an authorized administrative credential change.\u003C/p\u003E\n\u003Cp\u003EThis makes administrative credential changes a privileged operation.\u003C/p\u003E\n\u003Cp\u003EDo not expose this capability through ordinary self-service UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-a-users-credential\u0022\u003ERevoke a User\u0027s Credential\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.RevokeUserAsync(userKey,\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-administrative-recovery\u0022\u003EBegin Administrative Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.BeginResetUserAsync(userKey,\n new BeginResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-administrative-recovery\u0022\u003EComplete Administrative Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.CompleteResetUserAsync(userKey,\n new CompleteResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetToken = recoveryToken,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative recovery remains a proof-based workflow.\u003C/p\u003E\n\u003Cp\u003EThe server validates the relationship between the target user and the supplied identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-users-credential\u0022\u003EDelete a User\u0027s Credential\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHard deletion is a destructive administrative operation.\u003C/p\u003E\n\u003Cp\u003EIts availability and effects depend on server authorization and credential lifecycle policies.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations do not automatically publish the same local credential state events as self-service operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-changes-and-session-invalidation\u0022\u003E15. Credential Changes and Session Invalidation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth allows applications to configure how existing sessions are handled after credential changes and recovery.\u003C/p\u003E\n\u003Cp\u003EThe server exposes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthServerOptions.CredentialSessionPolicy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe policy contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOption\u003C/th\u003E\n\u003Cth\u003EDefault\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnSelfChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after self-service credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnAdminChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after administrative credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnRecovery\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after credential recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialsessionbehavior\u0022\u003ECredentialSessionBehavior\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic enum CredentialSessionBehavior\n{\n PreserveAll = 0,\n PreserveCurrent = 10,\n RevokeAll = 20\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current chain and revoke other chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-policy\u0022\u003EConfigure the Policy\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent;\n options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll;\n options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe defaults are intended to balance usability and security.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated user changing their password can retain their current device while other devices are invalidated.\u003C/p\u003E\n\u003Cp\u003EAn administrator changing a user\u0027s password typically requires broader session revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-and-preservecurrent\u0022\u003ERecovery and PreserveCurrent\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E is not meaningful for many recovery workflows because the user may not have an authenticated current session.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s recovery policy documentation specifies that \u003Ccode\u003EPreserveCurrent\u003C/code\u003E is treated as \u003Ccode\u003ERevokeAll\u003C/code\u003E in this context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h3\u003E\n\u003Cp\u003EThe precise effect of session invalidation depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, a locally validated JWT access token may remain usable until expiration if the resource server does not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ECredential session policies should be understood together with the application\u0027s token validation strategy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-credential-results\u0022\u003E16. Handling Credential Results\u003C/h2\u003E\n\u003Cp\u003ECredential operations use \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESeveral operations also return a domain-level success flag.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-creation\u0022\u003ECredential Creation\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.AddMyAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } add)\n{\n // Credential creation was rejected.\n Console.WriteLine(add.Error);\n}\nelse if (result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.CompleteResetAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n // Recovery did not complete successfully.\n}\nelse\n{\n // Recovery succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-status-helpers\u0022\u003EHTTP Status Helpers\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (result.Http.IsUnauthorized)\n{\n // Authentication required.\n}\nelse if (result.Http.IsForbidden)\n{\n // Operation not permitted.\n}\nelse if (result.Http.IsConflict)\n{\n // Conflicting or expired operation state.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actual status depends on the server\u0027s response.\u003C/p\u003E\n\u003Cp\u003EAvoid exposing sensitive internal credential errors directly to end users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-state-events\u0022\u003E17. Credential State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthCredentialClient\u003C/code\u003E publishes the following events after successful self-service operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe following operations do not publish those events in the current client implementation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddMyAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EAdministrative credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EApplications should refresh relevant data explicitly where needed.\u003C/p\u003E\n\u003Cp\u003EEvents help synchronize client state but do not themselves establish that a credential or session has been invalidated on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E18. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022never-expose-stored-secrets\u0022\u003ENever Expose Stored Secrets\u003C/h3\u003E\n\u003Cp\u003ECredential secrets must not be returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EApplications should never log passwords, reset proofs, or other authentication secrets.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-secure-transport\u0022\u003EUse Secure Transport\u003C/h3\u003E\n\u003Cp\u003ECredential operations must be performed over appropriately secured transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-recovery-proofs\u0022\u003EProtect Recovery Proofs\u003C/h3\u003E\n\u003Cp\u003ERecovery tokens and codes should be short-lived, single-use, and delivered through trusted channels.\u003C/p\u003E\n\u003Cp\u003EDo not store recovery proofs in persistent client storage unless the application has a carefully justified security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-self-service-and-administrative-operations-separate\u0022\u003EKeep Self-Service and Administrative Operations Separate\u003C/h3\u003E\n\u003Cp\u003ESelf-service changes require the current credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative changes are privileged and require server-side authorization.\u003C/p\u003E\n\u003Cp\u003EDo not substitute one for the other.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022preserve-account-recovery-safety\u0022\u003EPreserve Account Recovery Safety\u003C/h3\u003E\n\u003Cp\u003EAvoid revealing account existence through public recovery responses.\u003C/p\u003E\n\u003Cp\u003EApply appropriate rate limiting, proof attempt limits, and abuse controls.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022consider-session-consequences\u0022\u003EConsider Session Consequences\u003C/h3\u003E\n\u003Cp\u003EChanging or resetting a password can affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EApplications should explain the expected consequences to users, especially when other devices will be logged out.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-assume-every-credential-type-is-implemented\u0022\u003EDo Not Assume Every Credential Type Is Implemented\u003C/h3\u003E\n\u003Cp\u003EThe existence of a \u003Ccode\u003ECredentialType\u003C/code\u003E enum member does not establish that its complete lifecycle is supported.\u003C/p\u003E\n\u003Cp\u003EUse the currently implemented credential contracts and documented authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, logout, refresh, and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/identifiers\u0022\u003EIdentifiers\u003C/a\u003E \u2014 Manage user identifiers and ownership verification.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage profiles and user lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and revoke sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Understand token validation and revocation behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session invalidation and account-level security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n",
+ "Html": "\n\u003Cp\u003EUltimateAuth provides a dedicated client API for managing authentication credentials, including password creation, password changes, credential revocation, account recovery, and administrative credential operations.\u003C/p\u003E\n\u003Cp\u003EThrough \u003Ccode\u003EUAuthClient.Credentials\u003C/code\u003E, applications can implement credential management without manually constructing requests to the underlying endpoints.\u003C/p\u003E\n\u003Cp\u003ECredential management is security-sensitive. UltimateAuth separates authenticated credential changes from recovery workflows and provides configurable session invalidation policies.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the current \u003Ccode\u003EICredentialClient\u003C/code\u003E API, its request and response models, and practical implementation patterns.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003E1. Understanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential is authentication material associated with a user.\u003C/p\u003E\n\u003Cp\u003EPasswords are the primary credential type supported by the current credential management request contracts.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth also defines a broader \u003Ccode\u003ECredentialType\u003C/code\u003E enum for additional authentication mechanisms.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic enum CredentialType\n{\n Password = 0,\n OneTimeCode = 10,\n EmailOtp = 11,\n SmsOtp = 12,\n Totp = 30,\n Passkey = 100,\n Certificate = 200,\n ApiKey = 210,\n External = 1000\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese enum values do not mean every credential type is currently supported by the credential management API.\u003C/p\u003E\n\u003Cp\u003EFor example, \u003Ccode\u003EAddCredentialRequest\u003C/code\u003E currently accepts a secret but does not expose a credential type selector.\u003C/p\u003E\n\u003Cp\u003EDo not assume that passkeys, API keys, or external authentication credentials can be created through \u003Ccode\u003EAddMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022accessing-the-credential-client\u0022\u003E2. Accessing the Credential Client\u003C/h2\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into your Blazor component:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential operations are available through:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthClient.Credentials\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003ECredentials\u003C/code\u003E property exposes \u003Ccode\u003EICredentialClient\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EIts operations fall into three groups:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EGroup\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESelf-service\u003C/td\u003E\n\u003Ctd\u003EAdd, change, and revoke the current user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERecovery\u003C/td\u003E\n\u003Ctd\u003EBegin and complete a credential reset\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAdministration\u003C/td\u003E\n\u003Ctd\u003EManage another user\u0027s credentials\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003ERecovery operations are designed to support users who cannot authenticate normally.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-your-password\u0022\u003E3. Change Your Password\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EChangeMyAsync\u003C/code\u003E when an authenticated user knows their current password and wants to replace it.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password changed successfully.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialChangeResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022changecredentialrequest\u0022\u003EChangeCredentialRequest\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EType\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring?\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECurrent password; required for self-service changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Estring\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ENew password\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is nullable in the shared request contract because administrative changes do not require the user\u0027s existing password.\u003C/p\u003E\n\u003Cp\u003EFor self-service password changes, provide the current secret.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialchangeresult\u0022\u003ECredentialChangeResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the credential change succeeded\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional operation-level failure\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type affected by the change\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = currentPassword,\n NewSecret = newPassword\n });\n\nif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to change your password.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } change)\n{\n errorMessage = change.Error ?? \u0026quot;Password change failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been changed.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECheck both the API-level result and the operation-level result when processing credential changes.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-state-event\u0022\u003EClient State Event\u003C/h3\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003EChangeMyAsync\u003C/code\u003E operation, the SDK publishes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthStateEvent.CredentialsChangedSelf\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event uses the configured client state event handling mode.\u003C/p\u003E\n\u003Cp\u003EIt can help the application refresh authentication-related UI.\u003C/p\u003E\n\u003Cp\u003EIt does not replace the server\u0027s credential or session security checks.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-password-change-form\u0022\u003E4. Build a Password Change Form\u003C/h2\u003E\n\u003Cp\u003EThe following example demonstrates an authenticated password change form in Blazor.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Change Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;EditForm Model=\u0026quot;_model\u0026quot; OnValidSubmit=\u0026quot;ChangePasswordAsync\u0026quot;\u0026gt;\n \u0026lt;DataAnnotationsValidator /\u0026gt;\n\n \u0026lt;label\u0026gt;Current password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.CurrentPassword\u0026quot; /\u0026gt;\n\n \u0026lt;label\u0026gt;New password\u0026lt;/label\u0026gt;\n \u0026lt;InputText type=\u0026quot;password\u0026quot; @bind-Value=\u0026quot;_model.NewPassword\u0026quot; /\u0026gt;\n\n \u0026lt;button type=\u0026quot;submit\u0026quot; disabled=\u0026quot;@_busy\u0026quot;\u0026gt;Change password\u0026lt;/button\u0026gt;\n\u0026lt;/EditForm\u0026gt;\n\n@if (!string.IsNullOrWhiteSpace(_message))\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private readonly PasswordChangeModel _model = new();\n private bool _busy;\n private string? _message;\n\n private async Task ChangePasswordAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.ChangeMyAsync(\n new ChangeCredentialRequest\n {\n CurrentSecret = _model.CurrentPassword,\n NewSecret = _model.NewPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp;\n result.Value is { IsSuccess: true })\n {\n _model.CurrentPassword = \u0026quot;\u0026quot;;\n _model.NewPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password changed successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to change password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;The credential service is unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n\n private sealed class PasswordChangeModel\n {\n [System.ComponentModel.DataAnnotations.Required]\n public string CurrentPassword { get; set; } = \u0026quot;\u0026quot;;\n\n [System.ComponentModel.DataAnnotations.Required]\n public string NewPassword { get; set; } = \u0026quot;\u0026quot;;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis example demonstrates the client integration.\u003C/p\u003E\n\u003Cp\u003EA production form should additionally provide password confirmation, appropriate password policy feedback, and safeguards against repeated submissions.\u003C/p\u003E\n\u003Cp\u003ENever log password values or include them in application telemetry.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential\u0022\u003E5. Add a Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EAddMyAsync\u003C/code\u003E to add a credential to the current user.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.AddMyAsync(\n new AddCredentialRequest\n {\n Secret = password\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine($\u0026quot;Credential ID: {added.Id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialAddResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022addcredentialrequest\u0022\u003EAddCredentialRequest\u003C/h3\u003E\n\u003Cp\u003EThe current request contract is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record AddCredentialRequest\n{\n public required string Secret { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECredential type and source selection are not currently exposed by this request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialaddresult\u0022\u003ECredentialAddResult\u003C/h3\u003E\n\u003Cp\u003EThe response contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIsSuccess\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EWhether the credential was added\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EError\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EOptional failure information\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EId\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential ID, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECreated credential type, when available\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EAdding a credential is subject to the server\u0027s credential policies.\u003C/p\u003E\n\u003Cp\u003EFor example, the server may reject attempts to create a duplicate or otherwise unsupported credential.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-change-versus-password-recovery\u0022\u003E6. Password Change Versus Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between two different workflows.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EWorkflow\u003C/th\u003E\n\u003Cth\u003ETypical situation\u003C/th\u003E\n\u003Cth\u003EAPI\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword change\u003C/td\u003E\n\u003Ctd\u003EAuthenticated user knows the current password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPassword recovery\u003C/td\u003E\n\u003Ctd\u003EUser cannot authenticate using the existing password\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E \u2192 \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EA password change verifies the existing secret.\u003C/p\u003E\n\u003Cp\u003EPassword recovery uses a separate proof mechanism, such as a reset token or code delivered through a trusted channel.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EDo not use administrative password changes as a replacement for public password recovery.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-password-recovery\u0022\u003E7. Begin Password Recovery\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003EBeginResetAsync\u003C/code\u003E to initiate a credential recovery workflow.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } reset)\n{\n Console.WriteLine($\u0026quot;Expires at: {reset.ExpiresAt}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialBeginResetResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginresetcredentialrequest\u0022\u003EBeginResetCredentialRequest\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EUser identifier associated with the recovery request\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type to recover\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetCodeType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReset proof format\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022reset-code-types\u0022\u003EReset Code Types\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic enum ResetCodeType\n{\n Token = 0,\n Code = 10\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse \u003Ccode\u003EToken\u003C/code\u003E for a token-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003ECode\u003C/code\u003E for a code-based recovery workflow.\u003C/p\u003E\n\u003Cp\u003EThe delivery mechanism is determined by the application\u0027s configured server-side notification implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialbeginresetresult\u0022\u003ECredentialBeginResetResult\u003C/h3\u003E\n\u003Cp\u003EThe result contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record CredentialBeginResetResult\n{\n public DateTimeOffset ExpiresAt { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe reset token or code is not returned in the API response.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThis prevents the recovery endpoint from directly exposing the secret needed to take over an account.\u003C/p\u003E\n\u003Cp\u003EThe proof must be delivered through the configured trusted notification channel.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-password-recovery\u0022\u003E8. Complete Password Recovery\u003C/h2\u003E\n\u003Cp\u003EAfter the user receives a reset token or code, call \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = \u0026quot;john@example.com\u0026quot;,\n CredentialType = CredentialType.Password,\n ResetToken = resetToken,\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Password recovery completed.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe method returns:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthResult\u0026lt;CredentialActionResult\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completeresetcredentialrequest\u0022\u003ECompleteResetCredentialRequest\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EProperty\u003C/th\u003E\n\u003Cth\u003EDescription\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EIdentifier\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EIdentifier used for the recovery workflow\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialType\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential type being recovered\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EResetToken\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EDelivered recovery proof\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ENewSecret\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EReplacement secret\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe property is named \u003Ccode\u003EResetToken\u003C/code\u003E even when the selected reset format is a code.\u003C/p\u003E\n\u003Cp\u003EUse the exact contract property name.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialactionresult\u0022\u003ECredentialActionResult\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record CredentialActionResult\n{\n public bool IsSuccess { get; init; }\n public string? Error { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (!result.IsSuccess)\n{\n errorMessage = \u0026quot;Unable to complete password recovery.\u0026quot;;\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n errorMessage = action.Error ?? \u0026quot;Password recovery failed.\u0026quot;;\n}\nelse\n{\n successMessage = \u0026quot;Your password has been reset.\u0026quot;;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter a successful \u003Ccode\u003ECompleteResetAsync\u003C/code\u003E operation, the SDK publishes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe event is not the same as \u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E, which is published by \u003Ccode\u003EChangeMyAsync\u003C/code\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-recovery-using-a-numeric-code\u0022\u003E9. Complete Recovery Using a Numeric Code\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth supports code-based recovery through \u003Ccode\u003EResetCodeType.Code\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EBegin the recovery:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar begin = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Code\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen submit the code received by the user:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar complete = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = email,\n CredentialType = CredentialType.Password,\n ResetToken = enteredCode,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe same completion API handles token-based and code-based recovery.\u003C/p\u003E\n\u003Cp\u003EThe server is responsible for validating the submitted proof against the active recovery state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022build-a-forgot-password-form\u0022\u003E10. Build a Forgot Password Form\u003C/h2\u003E\n\u003Cp\u003EA typical forgot-password interface has two stages:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003ERequest a recovery token or code.\u003C/li\u003E\n\u003Cli\u003ESubmit the received proof with a new password.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-one-request-recovery\u0022\u003EStep One: Request Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Forgot Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; type=\u0026quot;email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;BeginRecoveryAsync\u0026quot;\u0026gt;Send recovery instructions\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string? _message;\n private bool _busy;\n\n private async Task BeginRecoveryAsync()\n {\n if (_busy || string.IsNullOrWhiteSpace(_email))\n return;\n\n _busy = true;\n\n try\n {\n var result = await UAuthClient.Credentials.BeginResetAsync(\n new BeginResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\n _message = result.IsSuccess\n ? \u0026quot;If the account is eligible, recovery instructions will be sent.\u0026quot;\n : \u0026quot;Unable to process the recovery request.\u0026quot;;\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe confirmation message intentionally avoids revealing whether an account exists.\u003C/p\u003E\n\u003Cp\u003EApplications should avoid introducing account enumeration through UI messages, timing differences, or diagnostic details.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022step-two-complete-recovery\u0022\u003EStep Two: Complete Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@using CodeBeam.UltimateAuth.Core.Domain\n@using CodeBeam.UltimateAuth.Credentials.Contracts\n@inject IUAuthClient UAuthClient\n\n\u0026lt;h3\u0026gt;Reset Password\u0026lt;/h3\u0026gt;\n\n\u0026lt;input @bind=\u0026quot;_email\u0026quot; placeholder=\u0026quot;Email address\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_token\u0026quot; placeholder=\u0026quot;Recovery token or code\u0026quot; /\u0026gt;\n\u0026lt;input @bind=\u0026quot;_newPassword\u0026quot; type=\u0026quot;password\u0026quot; placeholder=\u0026quot;New password\u0026quot; /\u0026gt;\n\n\u0026lt;button type=\u0026quot;button\u0026quot; disabled=\u0026quot;@_busy\u0026quot; @onclick=\u0026quot;CompleteRecoveryAsync\u0026quot;\u0026gt;Reset password\u0026lt;/button\u0026gt;\n\n@if (_message is not null)\n{\n \u0026lt;p role=\u0026quot;status\u0026quot;\u0026gt;@_message\u0026lt;/p\u0026gt;\n}\n\n@code {\n private string _email = \u0026quot;\u0026quot;;\n private string _token = \u0026quot;\u0026quot;;\n private string _newPassword = \u0026quot;\u0026quot;;\n\n private string? _message;\n private bool _busy;\n\n private async Task CompleteRecoveryAsync()\n {\n if (_busy)\n return;\n\n _busy = true;\n _message = null;\n\n try\n {\n var result = await UAuthClient.Credentials.CompleteResetAsync(\n new CompleteResetCredentialRequest\n {\n Identifier = _email,\n CredentialType = CredentialType.Password,\n ResetToken = _token,\n NewSecret = _newPassword\n });\n\n if (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n {\n _token = \u0026quot;\u0026quot;;\n _newPassword = \u0026quot;\u0026quot;;\n _message = \u0026quot;Password reset successfully.\u0026quot;;\n }\n else\n {\n _message = \u0026quot;Unable to reset password.\u0026quot;;\n }\n }\n catch (Exception)\n {\n _message = \u0026quot;Recovery is temporarily unavailable.\u0026quot;;\n }\n finally\n {\n _busy = false;\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThese examples show the SDK integration.\u003C/p\u003E\n\u003Cp\u003EA production implementation should also validate user input, protect recovery proofs from accidental disclosure, and avoid logging reset tokens or passwords.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-reset-notifications\u0022\u003E11. Configure Reset Notifications\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses \u003Ccode\u003EICredentialResetNotifier\u003C/code\u003E to deliver recovery tokens and codes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic interface ICredentialResetNotifier\n{\n Task NotifyAsync(CredentialResetNotification notification, CancellationToken ct = default);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe notification includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record CredentialResetNotification(\n TenantKey Tenant,\n UserKey UserKey,\n CredentialType CredentialType,\n ResetCodeType CodeType,\n string Token,\n DateTimeOffset ExpiresAt);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application must provide a secure delivery implementation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-notifier\u0022\u003ERegister the Notifier\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScoped\u0026lt;ICredentialResetNotifier, ApplicationCredentialResetNotifier\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EApplicationCredentialResetNotifier\u003C/code\u003E is an application-defined implementation.\u003C/p\u003E\n\u003Cp\u003EIt might deliver the proof through email, SMS, or another trusted channel associated with the user\u0027s account.\u003C/p\u003E\n\u003Cp\u003EThe notifier should not expose the proof to an arbitrary address supplied by an unauthenticated caller.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022missing-notifier-configuration\u0022\u003EMissing Notifier Configuration\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth provides a \u003Ccode\u003ENotConfiguredCredentialResetNotifier\u003C/code\u003E fallback.\u003C/p\u003E\n\u003Cp\u003EWhen recovery requires notification but no implementation has been configured, the fallback throws an explanatory exception.\u003C/p\u003E\n\u003Cp\u003EThis prevents a reset flow from silently succeeding without a configured proof delivery mechanism.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ENever return recovery tokens to anonymous clients as a substitute for configuring the notifier.\u003C/strong\u003E\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-security-behavior\u0022\u003E12. Recovery Security Behavior\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is designed around several security properties.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022account-enumeration-resistance\u0022\u003EAccount Enumeration Resistance\u003C/h3\u003E\n\u003Cp\u003ERecovery initiation should not reveal whether a submitted identifier belongs to an account.\u003C/p\u003E\n\u003Cp\u003EApplications should preserve this behavior in their own UI and logging policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022proof-expiration\u0022\u003EProof Expiration\u003C/h3\u003E\n\u003Cp\u003ERecovery proofs have a server-controlled lifetime.\u003C/p\u003E\n\u003Cp\u003EThe client does not choose an arbitrary expiration time.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-use-proofs\u0022\u003ESingle-Use Proofs\u003C/h3\u003E\n\u003Cp\u003EA successfully consumed recovery proof must not be reusable.\u003C/p\u003E\n\u003Cp\u003EAfter successful completion, a second attempt using the same proof should be rejected.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-and-target-binding\u0022\u003EIdentifier and Target Binding\u003C/h3\u003E\n\u003Cp\u003ERecovery must apply to the correct user and credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative recovery must not permit an identifier belonging to one user to reset another user\u0027s credential.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022session-revocation\u0022\u003ESession Revocation\u003C/h3\u003E\n\u003Cp\u003ESuccessful recovery may invalidate existing sessions according to the configured credential session policy.\u003C/p\u003E\n\u003Cp\u003EBy default, UltimateAuth revokes all session chains after recovery.\u003C/p\u003E\n\u003Cp\u003EFor further details, see \u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-your-credential\u0022\u003E13. Revoke Your Credential\u003C/h2\u003E\n\u003Cp\u003EUse \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E to revoke a credential belonging to the authenticated user.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.RevokeMyAsync(\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\nif (result.IsSuccess)\n{\n // Credential revoked.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe request contract is:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic sealed record RevokeCredentialRequest\n{\n public Guid Id { get; init; }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EUse the credential record ID, not a user ID or session ID.\u003C/p\u003E\n\u003Cp\u003ECredential revocation is distinct from credential deletion.\u003C/p\u003E\n\u003Cp\u003ERevocation changes the credential\u0027s usability, while deletion uses a separate lifecycle operation available through the administrative API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022state-event\u0022\u003EState Event\u003C/h3\u003E\n\u003Cp\u003EAfter successful \u003Ccode\u003ERevokeMyAsync\u003C/code\u003E, the SDK publishes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthStateEvent.CredentialsChanged\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe application can use this event to refresh credential-related UI.\u003C/p\u003E\n\u003Cp\u003ECredential revocation may affect authentication eligibility and session continuation depending on server policy.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-credential-management\u0022\u003E14. Administrative Credential Management\u003C/h2\u003E\n\u003Cp\u003EAdministrative credential operations target a specific user.\u003C/p\u003E\n\u003Cp\u003EThey require appropriate server-side authorization.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022add-a-credential-1\u0022\u003EAdd a Credential\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.AddUserAsync(userKey,\n new AddCredentialRequest\n {\n Secret = temporaryPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server validates whether the administrative actor may add a credential for the target user.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022change-a-users-password\u0022\u003EChange a User\u0027s Password\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.ChangeUserAsync(userKey,\n new ChangeCredentialRequest\n {\n NewSecret = newPassword\n });\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { IsSuccess: true })\n{\n // Administrative password change succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ECurrentSecret\u003C/code\u003E is not required for an authorized administrative credential change.\u003C/p\u003E\n\u003Cp\u003EThis makes administrative credential changes a privileged operation.\u003C/p\u003E\n\u003Cp\u003EDo not expose this capability through ordinary self-service UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022revoke-a-users-credential\u0022\u003ERevoke a User\u0027s Credential\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.RevokeUserAsync(userKey,\n new RevokeCredentialRequest\n {\n Id = credentialId\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022begin-administrative-recovery\u0022\u003EBegin Administrative Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.BeginResetUserAsync(userKey,\n new BeginResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetCodeType = ResetCodeType.Token\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022complete-administrative-recovery\u0022\u003EComplete Administrative Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.CompleteResetUserAsync(userKey,\n new CompleteResetCredentialRequest\n {\n Identifier = userEmail,\n CredentialType = CredentialType.Password,\n ResetToken = recoveryToken,\n NewSecret = newPassword\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAdministrative recovery remains a proof-based workflow.\u003C/p\u003E\n\u003Cp\u003EThe server validates the relationship between the target user and the supplied identifier.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022delete-a-users-credential\u0022\u003EDelete a User\u0027s Credential\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Soft\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hard-delete\u0022\u003EHard Delete\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.DeleteUserAsync(userKey,\n new DeleteCredentialRequest\n {\n Id = credentialId,\n Mode = DeleteMode.Hard\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EHard deletion is a destructive administrative operation.\u003C/p\u003E\n\u003Cp\u003EIts availability and effects depend on server authorization and credential lifecycle policies.\u003C/p\u003E\n\u003Cp\u003EAdministrative operations do not automatically publish the same local credential state events as self-service operations.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-changes-and-session-invalidation\u0022\u003E15. Credential Changes and Session Invalidation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth allows applications to configure how existing sessions are handled after credential changes and recovery.\u003C/p\u003E\n\u003Cp\u003EThe server exposes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003EUAuthServerOptions.CredentialSessionPolicy\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe policy contains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOption\u003C/th\u003E\n\u003Cth\u003EDefault\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnSelfChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after self-service credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnAdminChange\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after administrative credential changes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EOnRecovery\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ESessions after credential recovery\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentialsessionbehavior\u0022\u003ECredentialSessionBehavior\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Epublic enum CredentialSessionBehavior\n{\n PreserveAll = 0,\n PreserveCurrent = 10,\n RevokeAll = 20\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003Cth\u003EMeaning\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current chain and revoke other chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable session chains\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-policy\u0022\u003EConfigure the Policy\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.CredentialSessionPolicy.OnSelfChange = CredentialSessionBehavior.PreserveCurrent;\n options.CredentialSessionPolicy.OnAdminChange = CredentialSessionBehavior.RevokeAll;\n options.CredentialSessionPolicy.OnRecovery = CredentialSessionBehavior.RevokeAll;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe defaults are intended to balance usability and security.\u003C/p\u003E\n\u003Cp\u003EFor example, an authenticated user changing their password can retain their current device while other devices are invalidated.\u003C/p\u003E\n\u003Cp\u003EAn administrator changing a user\u0027s password typically requires broader session revocation.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022recovery-and-preservecurrent\u0022\u003ERecovery and PreserveCurrent\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EPreserveCurrent\u003C/code\u003E is not meaningful for many recovery workflows because the user may not have an authenticated current session.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s recovery policy documentation specifies that \u003Ccode\u003EPreserveCurrent\u003C/code\u003E is treated as \u003Ccode\u003ERevokeAll\u003C/code\u003E in this context.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-mode-considerations\u0022\u003EAuthentication Mode Considerations\u003C/h3\u003E\n\u003Cp\u003EThe precise effect of session invalidation depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn particular, a locally validated JWT access token may remain usable until expiration if the resource server does not consult current revocation state.\u003C/p\u003E\n\u003Cp\u003ECredential session policies should be understood together with the application\u0027s token validation strategy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022handling-credential-results\u0022\u003E16. Handling Credential Results\u003C/h2\u003E\n\u003Cp\u003ECredential operations use \u003Ccode\u003EUAuthResult\u003C/code\u003E or \u003Ccode\u003EUAuthResult\u0026lt;T\u0026gt;\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003ESeveral operations also return a domain-level success flag.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-creation\u0022\u003ECredential Creation\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.AddMyAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } add)\n{\n // Credential creation was rejected.\n Console.WriteLine(add.Error);\n}\nelse if (result.Value is { IsSuccess: true } added)\n{\n Console.WriteLine(added.Id);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.CompleteResetAsync(request);\n\nif (!result.IsSuccess)\n{\n // API-level failure.\n}\nelse if (result.Value is { IsSuccess: false } action)\n{\n // Recovery did not complete successfully.\n}\nelse\n{\n // Recovery succeeded.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022http-status-helpers\u0022\u003EHTTP Status Helpers\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (result.Http.IsUnauthorized)\n{\n // Authentication required.\n}\nelse if (result.Http.IsForbidden)\n{\n // Operation not permitted.\n}\nelse if (result.Http.IsConflict)\n{\n // Conflicting or expired operation state.\n}\nelse if (result.Http.IsBadRequest)\n{\n // Invalid request.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe actual status depends on the server\u0027s response.\u003C/p\u003E\n\u003Cp\u003EAvoid exposing sensitive internal credential errors directly to end users.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-state-events\u0022\u003E17. Credential State Events\u003C/h2\u003E\n\u003Cp\u003EThe current \u003Ccode\u003EUAuthCredentialClient\u003C/code\u003E publishes the following events after successful self-service operations.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EEvent\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EChangeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChangedSelf\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeMyAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ECompleteResetAsync\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003ECredentialsChanged\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe following operations do not publish those events in the current client implementation:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddMyAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EBeginResetAsync\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003EAdministrative credential operations\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EApplications should refresh relevant data explicitly where needed.\u003C/p\u003E\n\u003Cp\u003EEvents help synchronize client state but do not themselves establish that a credential or session has been invalidated on the server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003E18. Security Considerations\u003C/h2\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022never-expose-stored-secrets\u0022\u003ENever Expose Stored Secrets\u003C/h3\u003E\n\u003Cp\u003ECredential secrets must not be returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EApplications should never log passwords, reset proofs, or other authentication secrets.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-secure-transport\u0022\u003EUse Secure Transport\u003C/h3\u003E\n\u003Cp\u003ECredential operations must be performed over appropriately secured transport.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protect-recovery-proofs\u0022\u003EProtect Recovery Proofs\u003C/h3\u003E\n\u003Cp\u003ERecovery tokens and codes should be short-lived, single-use, and delivered through trusted channels.\u003C/p\u003E\n\u003Cp\u003EDo not store recovery proofs in persistent client storage unless the application has a carefully justified security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022keep-self-service-and-administrative-operations-separate\u0022\u003EKeep Self-Service and Administrative Operations Separate\u003C/h3\u003E\n\u003Cp\u003ESelf-service changes require the current credential.\u003C/p\u003E\n\u003Cp\u003EAdministrative changes are privileged and require server-side authorization.\u003C/p\u003E\n\u003Cp\u003EDo not substitute one for the other.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022preserve-account-recovery-safety\u0022\u003EPreserve Account Recovery Safety\u003C/h3\u003E\n\u003Cp\u003EAvoid revealing account existence through public recovery responses.\u003C/p\u003E\n\u003Cp\u003EApply appropriate rate limiting, proof attempt limits, and abuse controls.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022consider-session-consequences\u0022\u003EConsider Session Consequences\u003C/h3\u003E\n\u003Cp\u003EChanging or resetting a password can affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EApplications should explain the expected consequences to users, especially when other devices will be logged out.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022do-not-assume-every-credential-type-is-implemented\u0022\u003EDo Not Assume Every Credential Type Is Implemented\u003C/h3\u003E\n\u003Cp\u003EThe existence of a \u003Ccode\u003ECredentialType\u003C/code\u003E enum member does not establish that its complete lifecycle is supported.\u003C/p\u003E\n\u003Cp\u003EUse the currently implemented credential contracts and documented authentication flows.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022retrieving-password-requirements\u0022\u003ERetrieving Password Requirements\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth clients can retrieve the effective password policy configured by the authentication server.\u003C/p\u003E\n\u003Cp\u003EThis allows applications to display the current requirements during registration, user creation, and password changes without hardcoding password rules in the UI.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Credentials.GetPasswordPolicyAsync();\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is { } policy)\n{\n // Display the effective requirements.\n var minimumLength = policy.MinLength;\n var maximumLength = policy.MaxLength;\n var level = policy.Level;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe response includes the configured policy level, effective length and character requirements, and indicators for server-side common-password, user-similarity, and password-history checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-side-validation\u0022\u003EClient-Side Validation\u003C/h3\u003E\n\u003Cp\u003EUse \u003Ccode\u003EUAuthPasswordClientValidator\u003C/code\u003E to provide immediate feedback before submitting a password:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client;\n\nvar errors = UAuthPasswordClientValidator.Validate(\n password,\n policy);\n\nif (errors.Count == 0)\n{\n // Local character and length requirements are satisfied.\n // The server must still validate the submitted password.\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe validator returns \u003Ccode\u003EPasswordPolicyErrorCodes\u003C/code\u003E values for failed local requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EClient-side validation is advisory, not authoritative.\u003C/strong\u003E The server remains responsible for the complete password-policy decision, including common-password detection, user similarity, and password history.\u003C/p\u003E\n\u003Cp\u003EFor the built-in Basic, Standard, and Strict requirements, configuration examples, and password-history behavior, see \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/authentication\u0022\u003EAuthentication\u003C/a\u003E \u2014 Login, logout, refresh, and authentication flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/identifiers\u0022\u003EIdentifiers\u003C/a\u003E \u2014 Manage user identifiers and ownership verification.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/user-management\u0022\u003EUser Management\u003C/a\u003E \u2014 Manage profiles and user lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/session-management\u0022\u003ESession Management\u003C/a\u003E \u2014 Inspect and revoke sessions.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/token-security\u0022\u003EToken Security\u003C/a\u003E \u2014 Understand token validation and revocation behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand session invalidation and account-level security boundaries.\u003C/li\u003E\n\u003C/ul\u003E\n",
"Headings": [
{
"Id": "understanding-credentials",
@@ -298,12 +298,22 @@
"Text": "Do Not Assume Every Credential Type Is Implemented",
"Level": 1
},
+ {
+ "Id": "retrieving-password-requirements",
+ "Text": "Retrieving Password Requirements",
+ "Level": 0
+ },
+ {
+ "Id": "client-side-validation",
+ "Text": "Client-Side Validation",
+ "Level": 1
+ },
{
"Id": "next-steps",
"Text": "Next Steps",
"Level": 0
}
],
- "SourceHash": "DF1EFE42FAB4410FFCA4565BFB89489FAA283C600459D65A80F5FE5BE0DF1044",
+ "SourceHash": "8A73382F304D6EC208718BEDC7D300B180A397144C25FB5FCC4BCC3F330FAF8F",
"BuilderVersion": "1"
}
\ No newline at end of file
diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json
index fde9e879..0a8a077b 100644
--- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json
+++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/docs-index.json
@@ -272,6 +272,13 @@
"Group": "security",
"GroupOrder": 8
},
+ {
+ "Title": "Password Policy \u0026 History",
+ "Slug": "security/password-policy",
+ "Order": 6,
+ "Group": "security",
+ "GroupOrder": 8
+ },
{
"Title": "Rate Limiting",
"Slug": "security/rate-limiting",
diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json
index 17e02bae..819aab59 100644
--- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json
+++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/getting-started/real-world-setup.json
@@ -1,7 +1,7 @@
{
"Slug": "getting-started/real-world-setup",
"Title": "Real World Setup",
- "Html": "\n\u003Cp\u003E\u003Cstrong\u003EOne authentication framework. Different deployment models.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E demonstrates UltimateAuth with Blazor Server and in-memory storage.\u003C/p\u003E\n\u003Cp\u003EReal applications may require persistent data, a separate authentication server, browser-based clients, or independently deployed APIs.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these scenarios through different service registrations and hosting integrations. You can choose the architecture that fits your application without configuring every available component.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the setup patterns demonstrated by the UltimateAuth samples.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-architecture\u0022\u003E1. Choose Your Architecture\u003C/h2\u003E\n\u003Cp\u003EStart by identifying where authentication is hosted and how your application communicates with it.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication architecture\u003C/th\u003E\n\u003Cth\u003EAuthentication host\u003C/th\u003E\n\u003Cth\u003EApplication registration\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server, single project\u003C/td\u003E\n\u003Ctd\u003ESame application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B persistence \u002B Blazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server with EF Core\u003C/td\u003E\n\u003Ctd\u003ESame application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B EF Core \u002B Blazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDedicated UAuthHub\u003C/td\u003E\n\u003Ctd\u003ESeparate authentication application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B persistence \u002B Hub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStandalone Blazor WASM\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EBlazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Web App, Interactive WASM\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EHost integration \u002B WASM client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeparate Resource API\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EResource API integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese models can be combined.\u003C/p\u003E\n\u003Cp\u003EFor example, an application might use an EF Core-backed UAuthHub, a standalone Blazor WASM frontend, and a separate Resource API.\u003C/p\u003E\n\u003Cp\u003EThe frontend handles user interaction, UAuthHub manages authentication, and the Resource API enforces access to protected resources.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistent-storage-with-entity-framework-core\u0022\u003E2. Persistent Storage with Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor applications that need authentication state to survive restarts, use a persistent storage provider.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides an Entity Framework Core bundle that registers its reference domain implementations and EF Core-backed stores.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\ndotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe SQLite provider is used here for simplicity. You can use another compatible EF Core database provider.\u003C/p\u003E\n\u003Cp\u003EWhen using the EF Core bundle, you do not need the in-memory bundle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-services\u0022\u003ERegister the services\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing CodeBeam.UltimateAuth.Server.Extensions;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;, sqlite =\u0026gt; sqlite.MigrationsAssembly(typeof(Program).Assembly.GetName().Name!));\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthEntityFrameworkCore()\u003C/code\u003E registers the EF Core stores and reference domain implementations.\u003C/p\u003E\n\u003Cp\u003EThe bundle provides a unified \u003Ccode\u003EUAuthDbContext\u003C/code\u003E for the supported authentication domains.\u003C/p\u003E\n\u003Cp\u003EYou do not need to register each user, session, credential, token, or authorization store individually.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-database-connection\u0022\u003EConfigure the database connection\u003C/h3\u003E\n\u003Cp\u003EFor a deployed application, prefer configuration over hardcoded connection strings.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003Eappsettings.json\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EJSON\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-json\u0022\u003E{\n \u0026quot;ConnectionStrings\u0026quot;: {\n \u0026quot;UltimateAuth\u0026quot;: \u0026quot;Data Source=uauth.db\u0026quot;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar connectionString = builder.Configuration.GetConnectionString(\u0026quot;UltimateAuth\u0026quot;)\n ?? throw new InvalidOperationException(\u0026quot;UltimateAuth connection string is missing.\u0026quot;);\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(connectionString);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor production databases, use your deployment environment\u0027s secret management facilities rather than storing credentials in source control.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations when the EF Core bundle is registered.\u003C/p\u003E\n\u003Cp\u003EYour application owns the database migration lifecycle.\u003C/p\u003E\n\u003Cp\u003EInstall the EF Core CLI if necessary:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECreate the initial migration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EApply it:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor Visual Studio Package Manager Console:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EPOWERSHELL\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-powershell\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EInstall \u003Ccode\u003EMicrosoft.EntityFrameworkCore.Tools\u003C/code\u003E if required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022applying-migrations-programmatically\u0022\u003EApplying migrations programmatically\u003C/h3\u003E\n\u003Cp\u003EThe EF Core samples demonstrate initialization through a scoped \u003Ccode\u003EUAuthDbContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor a controlled development environment:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\nusing (var scope = app.Services.CreateScope())\n{\n var db = scope.ServiceProvider.GetRequiredService\u0026lt;UAuthDbContext\u0026gt;();\n await db.Database.MigrateAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis applies pending migrations. It does not generate migration files.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EProduction guidance:\u003C/strong\u003E Prefer applying reviewed migrations as a separate deployment step, particularly when multiple application instances share a database.\u003C/p\u003E\n\u003Cp\u003EDo not copy the sample initializer\u0027s \u003Ccode\u003Ereset: true\u003C/code\u003E behavior into production. The samples use database reset functionality for repeatable demonstrations, not as a safe deployment strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022optional-sample-accounts-with-ef-core\u0022\u003EOptional: sample accounts with EF Core\u003C/h3\u003E\n\u003Cp\u003EThe sample seed package can also work with EF Core persistence.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the scoped seed services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScopedUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter preparing the database:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe difference matters:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E is used by the in-memory samples.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAddScopedUltimateAuthSampleSeed()\u003C/code\u003E is used by the EF Core samples.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDo not deploy the demonstration accounts and passwords to production.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-integrated-authentication\u0022\u003E3. Blazor Server \u2014 Integrated Authentication\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A Blazor Server application that wants to host its own authentication system.\u003C/p\u003E\n\u003Cp\u003EThis is the simplest deployment model for a server-side Blazor application.\u003C/p\u003E\n\u003Cp\u003EThe application hosts UltimateAuth Server and uses the Blazor client adapter in the same project.\u003C/p\u003E\n\u003Cp\u003EA separate UAuthHub is not required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022packages\u0022\u003EPackages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022services\u0022\u003EServices\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing CodeBeam.UltimateAuth.Server.Extensions;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services\n .AddRazorComponents()\n .AddInteractiveServerComponents();\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pipeline\u0022\u003EPipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\n\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAlso configure the \u003Ccode\u003EUAuthApp\u003C/code\u003E root component, login page, and UltimateAuth JavaScript file as demonstrated in QuickStart.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-behavior\u0022\u003EAuthentication behavior\u003C/h3\u003E\n\u003Cp\u003EThe default client profile resolution selects \u003Ccode\u003EPureOpaque\u003C/code\u003E for Blazor Server.\u003C/p\u003E\n\u003Cp\u003EThis uses session-oriented authentication rather than requiring the application to manage access and refresh tokens manually.\u003C/p\u003E\n\u003Cp\u003EYou can configure session lifetimes, login policies, credential policies, and other behavior through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Session.Lifetime = TimeSpan.FromDays(7);\n options.Identifiers.Behavior.AllowMultipleUsernames = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective authentication mode is resolved at runtime. The application does not need to select a single mode globally.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthhub-dedicated-authentication-server\u0022\u003E4. UAuthHub \u2014 Dedicated Authentication Server\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E Applications where multiple clients share one authentication authority.\u003C/p\u003E\n\u003Cp\u003EUAuthHub hosts UltimateAuth\u0027s authentication services separately from your frontend applications.\u003C/p\u003E\n\u003Cp\u003EIt is particularly relevant for standalone WASM applications, Interactive WASM applications, and systems with separate Resource APIs.\u003C/p\u003E\n\u003Cp\u003EA typical architecture looks like this:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Standalone WASM Interactive WASM\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n UAuthHub\n Authentication\n Sessions \u0026amp; Users\n \u2502\n \u25BC\n Persistence\n\n Resource API \u2500\u2500\u2500\u25BA UAuthHub\n (validation)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-the-hub-application\u0022\u003ECreate the Hub application\u003C/h3\u003E\n\u003Cp\u003EA UAuthHub can be hosted in an ASP.NET Core Blazor Server application.\u003C/p\u003E\n\u003Cp\u003EThe current samples use Blazor Server to provide the Hub\u0027s login UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-memory-uauthhub\u0022\u003EIn-memory UAuthHub\u003C/h3\u003E\n\u003Cp\u003EFor development:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(options =\u0026gt;\n {\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub\u0027s Blazor client registration is needed when the Hub itself hosts UltimateAuth-powered Blazor pages and login UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ef-core-uauthhub\u0022\u003EEF Core UAuthHub\u003C/h3\u003E\n\u003Cp\u003EFor persistent storage:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauthhub.db\u0026quot;);\n })\n .AddUAuthHub(options =\u0026gt;\n {\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E collection identifies browser origins permitted to interact with the Hub.\u003C/p\u003E\n\u003Cp\u003EUse exact origins, including scheme and port. Do not include paths such as \u003Ccode\u003E/home\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-pipeline\u0022\u003EHub pipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseHttpsRedirection();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\n\napp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\napp.MapStaticAssets();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E maps the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUAuthHub()\u003C/code\u003E maps the Hub-specific entry endpoint.\u003C/p\u003E\n\u003Cp\u003EBoth registrations are used by the current UAuthHub samples.\u003C/p\u003E\n\u003Cp\u003EThe Hub also needs its login page and Blazor application lifecycle components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-origins\u0022\u003EChoosing origins\u003C/h3\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication\u003C/th\u003E\n\u003Cth\u003EURL\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6112\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStandalone WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6130\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInteractive WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6132\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EConfigure the Hub\u0027s allowed origins for the actual frontend applications it serves.\u003C/p\u003E\n\u003Cp\u003EThese localhost addresses are illustrative and must match your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-webassembly\u0022\u003E5. Blazor Standalone WebAssembly\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A browser-hosted application with a separate UAuthHub.\u003C/p\u003E\n\u003Cp\u003EStandalone Blazor WASM runs in the browser. It does not host UltimateAuth Server or database persistence.\u003C/p\u003E\n\u003Cp\u003EAuthentication is handled by the remote UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-packages\u0022\u003EClient packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure services\u003C/h3\u003E\n\u003Cp\u003EIn the standalone WASM application\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.RootComponents.Add\u0026lt;App\u0026gt;(\u0026quot;#app\u0026quot;);\nbuilder.RootComponents.Add\u0026lt;HeadOutlet\u0026gt;(\u0026quot;head::after\u0026quot;);\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nawait builder.Build().RunAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current standalone WASM sample explicitly configures \u003Ccode\u003ELogin.AllowCredentialPost\u003C/code\u003E and the PKCE return URL.\u003C/p\u003E\n\u003Cp\u003EThese are client-side settings. The UAuthHub must be configured independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-urls-mean\u0022\u003EWhat the URLs mean\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E points to the remote authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EPkce.ReturnUrl\u003C/code\u003E identifies the frontend destination used by the relevant PKCE flow.\u003C/p\u003E\n\u003Cp\u003EThe Hub must recognize the frontend\u0027s origin, and any return URL used in an authentication flow must be validated against the application\u0027s security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-component\u0022\u003ERoot component\u003C/h3\u003E\n\u003Cp\u003EA standalone WASM application should initialize UltimateAuth through \u003Ccode\u003EUAuthApp\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, its root application or routing component can contain:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EInclude the UltimateAuth JavaScript asset in the application\u0027s HTML host page.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes\u0022\u003EAuthentication modes\u003C/h3\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EBlazorWasm\u003C/code\u003E profile resolves to \u003Ccode\u003EHybrid\u003C/code\u003E in the current mode resolver.\u003C/p\u003E\n\u003Cp\u003EDo not manually force \u003Ccode\u003EPureOpaque\u003C/code\u003E for a standalone browser client simply because a Blazor Server application uses it.\u003C/p\u003E\n\u003Cp\u003EThe transport and authentication lifecycle must match the client environment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-interactive-webassembly\u0022\u003E6. Blazor Web App \u2014 Interactive WebAssembly\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A Blazor Web App with an ASP.NET Core host and a separate \u003Ccode\u003E.Client\u003C/code\u003E project.\u003C/p\u003E\n\u003Cp\u003EThis scenario differs from standalone WASM because components may participate in both server-side rendering and browser-side interactivity.\u003C/p\u003E\n\u003Cp\u003EThe current UltimateAuth sample registers the Blazor client integration in \u003Cstrong\u003Eboth projects\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also adds a separate ASP.NET Core compatibility registration in the host.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022project-structure\u0022\u003EProject structure\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EMyApplication/\n\u251C\u2500\u2500 MyApplication/\n\u2502 \u251C\u2500\u2500 Program.cs\n\u2502 \u2514\u2500\u2500 Components/\n\u2502 \u2514\u2500\u2500 App.razor\n\u2502\n\u2514\u2500\u2500 MyApplication.Client/\n \u251C\u2500\u2500 Program.cs\n \u2514\u2500\u2500 Pages/\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host serves the application.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003E.Client\u003C/code\u003E project contains browser-executed components and services.\u003C/p\u003E\n\u003Cp\u003EA remote UAuthHub manages authentication.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-packages\u0022\u003E6.1 Host project packages\u003C/h3\u003E\n\u003Cp\u003EInstall the Blazor client integration and ASP.NET Core compatibility package in the host:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.Client.AspNetCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-services\u0022\u003E6.2 Host project services\u003C/h3\u003E\n\u003Cp\u003EIn the host \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.AspNetCore;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nbuilder.Services\n .AddRazorComponents()\n .AddInteractiveWebAssemblyComponents();\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nbuilder.Services.AddUltimateAuthAspNetCoreCompatibility();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthAspNetCoreCompatibility()\u003C/code\u003E registers the compatibility authentication scheme and ASP.NET Core authorization services used by this hosting pattern.\u003C/p\u003E\n\u003Cp\u003EIt does \u003Cstrong\u003Enot\u003C/strong\u003E turn the host into an UltimateAuth authentication server.\u003C/p\u003E\n\u003Cp\u003EIt is also not equivalent to \u003Ccode\u003EAddUltimateAuthResourceApi()\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-pipeline\u0022\u003E6.3 Host project pipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseHttpsRedirection();\n\napp.UseAntiforgery();\n\napp.MapStaticAssets();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(typeof(MyApplication.Client._Imports).Assembly, UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EReplace \u003Ccode\u003EMyApplication.Client._Imports\u003C/code\u003E with the corresponding type from your client project.\u003C/p\u003E\n\u003Cp\u003EThe additional assemblies allow the host to discover routable components from the WASM client project and UltimateAuth\u0027s Blazor client assembly.\u003C/p\u003E\n\u003Cp\u003EThe sample does not register \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E in this host because authentication is delegated to a separate UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-project-packages\u0022\u003E6.4 Client project packages\u003C/h3\u003E\n\u003Cp\u003EInstall the Blazor client adapter in the \u003Ccode\u003E.Client\u003C/code\u003E project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-project-services\u0022\u003E6.5 Client project services\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EMyApplication.Client/Program.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nawait builder.Build().RunAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host and client configurations should agree on the remote UAuthHub URL and authentication flow settings.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-both-registrations-are-necessary\u0022\u003EWhy both registrations are necessary\u003C/h3\u003E\n\u003Cp\u003EThe host and browser client have separate dependency injection containers.\u003C/p\u003E\n\u003Cp\u003ERegistering \u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E in the host does not automatically register those services inside the WebAssembly runtime.\u003C/p\u003E\n\u003Cp\u003ELikewise, registering the client adapter only in the \u003Ccode\u003E.Client\u003C/code\u003E project does not supply the services required during server-side integration.\u003C/p\u003E\n\u003Cp\u003EThe additional ASP.NET Core compatibility registration addresses the host\u0027s ASP.NET Core authentication and authorization integration requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E This compatibility layer is not a replacement for server-side authorization of your protected business APIs.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-separate-protected-backend\u0022\u003E7. Resource API \u2014 Separate Protected Backend\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E An API that needs UltimateAuth authentication validation but does not host the authentication system itself.\u003C/p\u003E\n\u003Cp\u003EA Resource API can be an ASP.NET Core Web API, MVC application, or another supported server-side application.\u003C/p\u003E\n\u003Cp\u003EIt does not need to be a Blazor application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022architecture\u0022\u003EArchitecture\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Blazor Client\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n UAuthHub Resource API\n \u25B2 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Authentication\n validation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Resource API uses the configured UAuthHub for remote authentication and session validation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-the-server-integration-package\u0022\u003EInstall the server integration package\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-1\u0022\u003EConfigure services\u003C/h3\u003E\n\u003Cp\u003EIn the Resource API\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Server.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddControllers();\n\nbuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://localhost:6112\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n options.CorsPolicyName = \u0026quot;resourceApi\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthHubBaseUrl\u003C/code\u003E is the base address of the authentication server.\u003C/p\u003E\n\u003Cp\u003ENotice that this example uses the Hub origin without the \u003Ccode\u003E/auth\u003C/code\u003E suffix, matching the current Resource API samples.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E controls the browser origins permitted by the Resource API\u0027s CORS configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-pipeline\u0022\u003EConfigure the pipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar app = builder.Build();\n\napp.UseHttpsRedirection();\n\napp.UseUltimateAuthResourceApiWithAspNetCore();\n\napp.MapControllers();\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthResourceApiWithAspNetCore()\u003C/code\u003E installs the Resource API integration middleware together with ASP.NET Core authentication and authorization middleware.\u003C/p\u003E\n\u003Cp\u003EYou do not need to register a local UltimateAuth Server or its persistence bundle in the Resource API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protecting-endpoints\u0022\u003EProtecting endpoints\u003C/h3\u003E\n\u003Cp\u003EUse ASP.NET Core authorization attributes for endpoints that require authentication:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\n\n[ApiController]\n[Route(\u0026quot;api/products\u0026quot;)]\npublic class ProductsController : ControllerBase\n{\n [HttpGet]\n [Authorize]\n public IActionResult GetProducts()\n {\n return Ok(new[]\n {\n new { Id = 1, Name = \u0026quot;Example product\u0026quot; }\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authorization attribute is enforced by ASP.NET Core on the server.\u003C/p\u003E\n\u003Cp\u003EFor permission-specific rules, configure and apply the corresponding authorization policies.\u003C/p\u003E\n\u003Cp\u003EDo not rely on Blazor UI authorization checks to protect Resource API endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cors-configuration\u0022\u003ECORS configuration\u003C/h3\u003E\n\u003Cp\u003EThe current Resource API integration registers CORS from the configured allowed origins.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnly add origins that should be allowed to call your API from a browser.\u003C/p\u003E\n\u003Cp\u003ECORS is a browser access control mechanism, not an authentication or authorization mechanism.\u003C/p\u003E\n\u003Cp\u003EA request must still pass the applicable authentication and authorization checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-vs-uauthhub\u0022\u003EResource API vs UAuthHub\u003C/h3\u003E\n\u003Cp\u003EThese are different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUAuthHub\u003C/th\u003E\n\u003Cth\u003EResource API\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHosts authentication flows\u003C/td\u003E\n\u003Ctd\u003EHosts application resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EManages authentication sessions\u003C/td\u003E\n\u003Ctd\u003EValidates remote authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUses authentication persistence\u003C/td\u003E\n\u003Ctd\u003EDoes not require local authentication stores\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProvides login and related endpoints\u003C/td\u003E\n\u003Ctd\u003EProvides application endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServes authentication clients\u003C/td\u003E\n\u003Ctd\u003EServes authorized API consumers\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUse a Resource API when you want independent deployment of business endpoints without duplicating the authentication server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-with-an-ef-core-backed-uauthhub\u0022\u003E8. Resource API with an EF Core-Backed UAuthHub\u003C/h2\u003E\n\u003Cp\u003EThe Resource API does not need to use EF Core just because UAuthHub does.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EInteractive WASM\n \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA Resource API\n \u2502\n \u25BC\n UAuthHub\n \u2502\n \u25BC\n EF Core Database\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub stores users, credentials, sessions, and related security state in its database.\u003C/p\u003E\n\u003Cp\u003EThe Resource API communicates with the Hub through the configured remote validation integration.\u003C/p\u003E\n\u003Cp\u003EIts registration remains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://localhost:6112\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n options.CorsPolicyName = \u0026quot;resourceApi\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation allows the API and authentication infrastructure to be deployed, scaled, and maintained independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-a-resource-api-from-blazor\u0022\u003E9. Calling a Resource API from Blazor\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides a unified Resource Client for accessing protected application APIs.\u003C/p\u003E\n\u003Cp\u003EInstead of configuring a separate HTTP client and implementing browser credential handling for ordinary resource requests, applications can use \u003Ccode\u003EIUAuthClient.Resource\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis keeps authentication, identity operations, and application resource requests accessible through the same client SDK.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-resource-api\u0022\u003EConfigure the Resource API\u003C/h3\u003E\n\u003Cp\u003EFor a separate Resource API, configure its origin through the Blazor client registration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client;\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n\n options.Resource.BaseAddress = \u0026quot;https://localhost:6122\u0026quot;;\n options.Resource.Credentials = UAuthResourceCredentialsMode.Auto;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E identifies the authentication server.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EResource.BaseAddress\u003C/code\u003E identifies the application Resource API.\u003C/p\u003E\n\u003Cp\u003EThese addresses can point to different applications.\u003C/p\u003E\n\u003Cp\u003EIf the Resource API shares the application\u0027s origin, \u003Ccode\u003EResource.BaseAddress\u003C/code\u003E can remain \u003Ccode\u003Enull\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022call-protected-resources\u0022\u003ECall Protected Resources\u003C/h3\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor component:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen retrieve application data:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Resource.GetAsync\u0026lt;List\u0026lt;Product\u0026gt;\u0026gt;(\u0026quot;/api/products\u0026quot;);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is not null)\n{\n products = result.Value;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Resource Client also supports POST, PUT, PATCH, and DELETE operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-specific-api-services\u0022\u003EApplication-Specific API Services\u003C/h3\u003E\n\u003Cp\u003EApplications can organize resource operations into small domain-specific services.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client;\nusing CodeBeam.UltimateAuth.Core.Contracts;\n\npublic sealed class ProductApiService(IUAuthClient client)\n{\n public Task\u0026lt;UAuthResult\u0026lt;List\u0026lt;Product\u0026gt;\u0026gt;\u0026gt; GetAllAsync()\n =\u0026gt; client.Resource.GetAsync\u0026lt;List\u0026lt;Product\u0026gt;\u0026gt;(\u0026quot;/api/products\u0026quot;);\n\n public Task\u0026lt;UAuthResult\u0026lt;Product\u0026gt;\u0026gt; CreateAsync(Product product)\n =\u0026gt; client.Resource.PostAsync\u0026lt;Product\u0026gt;(\u0026quot;/api/products\u0026quot;,product);\n\n public Task\u0026lt;UAuthResult\u0026gt; DeleteAsync(int id)\n =\u0026gt; client.Resource.DeleteAsync($\u0026quot;/api/products/{id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the application service:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScoped\u0026lt;ProductApiService\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe service uses UltimateAuth\u0027s transport and result contracts rather than implementing another HTTP abstraction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-webassembly\u0022\u003EInteractive WebAssembly\u003C/h3\u003E\n\u003Cp\u003EIn an Interactive WebAssembly application, configure the UltimateAuth Blazor client in each project where the Resource Client will be used.\u003C/p\u003E\n\u003Cp\u003EThe host and \u003Ccode\u003E.Client\u003C/code\u003E project have separate dependency injection containers.\u003C/p\u003E\n\u003Cp\u003EA shared application API service may be registered in both projects when components execute in both environments.\u003C/p\u003E\n\u003Cp\u003EThe Resource Client uses browser-based transport, including when invoked from an interactive Blazor Server component.\u003C/p\u003E\n\u003Cp\u003EBrowser interop must be available before making resource requests. Do not initiate browser-dependent resource requests during static prerendering.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-webassembly\u0022\u003EStandalone WebAssembly\u003C/h3\u003E\n\u003Cp\u003EStandalone Blazor WebAssembly applications use the same Resource Client API.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Resource.BaseAddress = \u0026quot;https://localhost:6122\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnce configured, application components can call protected resources through \u003Ccode\u003EIUAuthClient.Resource\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA separate application-specific \u003Ccode\u003EHttpClient\u003C/code\u003E is not required for these requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022browser-credentials-and-cors\u0022\u003EBrowser Credentials and CORS\u003C/h3\u003E\n\u003Cp\u003EFor cross-origin requests, the browser and receiving API must agree on credential handling.\u003C/p\u003E\n\u003Cp\u003EThe Resource Client supports \u003Ccode\u003EAuto\u003C/code\u003E, \u003Ccode\u003ESameOrigin\u003C/code\u003E, \u003Ccode\u003EInclude\u003C/code\u003E, and \u003Ccode\u003EOmit\u003C/code\u003E credential policies.\u003C/p\u003E\n\u003Cp\u003EFor cross-origin cookie-based authentication, configure the appropriate credential policy and ensure that the Resource API supports the required CORS and cookie settings.\u003C/p\u003E\n\u003Cp\u003EThe Resource Client does not automatically make an API authenticated merely because the user has signed in through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EThe receiving API must validate the authentication material associated with the configured authentication mode and enforce its authorization policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-and-direct-service-calls\u0022\u003EBlazor Server and Direct Service Calls\u003C/h3\u003E\n\u003Cp\u003EBlazor Server applications can often access application services directly when those services are hosted in the same process.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar products = await productService.GetAllAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThere is no need to introduce an HTTP request solely to use the Resource Client.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIUAuthClient.Resource\u003C/code\u003E when the application actually needs to communicate with a protected Resource API through the browser.\u003C/p\u003E\n\u003Cp\u003EFor server-to-server requests, use an appropriate server-side HTTP client and authentication mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022learn-more\u0022\u003ELearn More\u003C/h3\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/resource-api-requests\u0022\u003EResource API Requests\u003C/a\u003E for supported operations, result handling, credential configuration, security considerations, and request cancellation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes-and-client-profiles\u0022\u003E10. Authentication Modes and Client Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates \u003Cstrong\u003Eclient profile detection\u003C/strong\u003E from \u003Cstrong\u003Eauthentication mode enforcement\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe default resolver maps client profiles to authentication modes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EDefault resolved mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EWeb Server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMAUI\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAPI\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis mapping describes the resolver\u0027s behavior, not a guarantee that every mode is currently fully supported by the server.\u003C/p\u003E\n\u003Cp\u003EIn particular, \u003Ccode\u003EPureJwt\u003C/code\u003E and \u003Ccode\u003ESemiHybrid\u003C/code\u003E have implementation limitations in the current release.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022restrict-allowed-modes\u0022\u003ERestrict allowed modes\u003C/h3\u003E\n\u003Cp\u003EOn the authentication server:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.AllowedModes =\n [\n UAuthMode.PureOpaque,\n UAuthMode.Hybrid\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E is a guardrail.\u003C/p\u003E\n\u003Cp\u003EIt does not select a mode for every request.\u003C/p\u003E\n\u003Cp\u003EThe effective mode is resolved from the client profile and then checked against the server\u0027s allowed-mode policy.\u003C/p\u003E\n\u003Cp\u003EFor most applications, use the default mode resolution unless you have a specific integration requirement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-options-for-real-applications\u0022\u003E11. Server Options for Real Applications\u003C/h2\u003E\n\u003Cp\u003EProduction configuration should reflect your application\u0027s security requirements.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Identifiers.Behavior.AllowMultipleUsernames = true;\n options.UserProfile.EnableMultiProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer options also compose the Core-defined login, session, token, PKCE, and multi-tenant options.\u003C/p\u003E\n\u003Cp\u003EConfigure those through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EDo not independently configure Core options and assume the server will automatically use them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-files\u0022\u003EConfiguration files\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth also supports configuration through \u003Ccode\u003Eappsettings.json\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EJSON\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5,\n \u0026quot;LockoutDuration\u0026quot;: \u0026quot;00:15:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server options registration binds the \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E configuration section.\u003C/p\u003E\n\u003Cp\u003ERemember that effective mode defaults and mode-specific overrides are applied after the base options have been configured.\u003C/p\u003E\n\u003Cp\u003EFor detailed configuration behavior, see Server Options and Advanced Configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-ip-address-reverse-proxy\u0022\u003E12. Client IP Address \u0026amp; Reverse Proxy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses ASP.NET Core\u0027s resolved client IP address for device information and IP-based rate limiting. It does not directly trust \u003Ccode\u003EX-Forwarded-For\u003C/code\u003E or \u003Ccode\u003ECF-Connecting-IP\u003C/code\u003E headers.\u003C/p\u003E\n\u003Cp\u003EIf your application runs behind Caddy, Nginx, or another reverse proxy, configure ASP.NET Core to trust that proxy.\u003C/p\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E, before \u003Ccode\u003Ebuilder.Build()\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing System.Net;\nusing Microsoft.AspNetCore.HttpOverrides;\n\nbuilder.Services.Configure\u0026lt;ForwardedHeadersOptions\u0026gt;(options =\u0026gt;\n{\n options.ForwardedHeaders =\n ForwardedHeaders.XForwardedFor |\n ForwardedHeaders.XForwardedProto;\n\n // Replace with your actual Caddy or Nginx proxy IP.\n options.KnownProxies.Add(IPAddress.Parse(\u0026quot;172.20.0.5\u0026quot;));\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen enable the middleware \u003Cstrong\u003Ebefore\u003C/strong\u003E UltimateAuth and HTTPS redirection:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseForwardedHeaders();\n\napp.UseHttpsRedirection();\napp.UseUltimateAuthWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe example IP \u003Ccode\u003E172.20.0.5\u003C/code\u003E represents a trusted reverse proxy, not the visitor\u0027s IP address. Replace it with your actual proxy IP. For Docker deployments, ensure the proxy address is stable or configure a narrowly scoped trusted network.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Never trust arbitrary forwarded headers or clear the trusted proxy lists to accept all sources. If your application connects directly to Kestrel without a reverse proxy, no forwarded headers configuration is required.\u003C/p\u003E\n\u003Cp\u003EFor multi-proxy deployments, configure the complete trusted proxy chain and forwarding behavior according to your infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022development-and-production-differences\u0022\u003E13. Development and Production Differences\u003C/h2\u003E\n\u003Cp\u003EThe samples intentionally prioritize visibility and repeatability.\u003C/p\u003E\n\u003Cp\u003EA production application needs additional safeguards.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EArea\u003C/th\u003E\n\u003Cth\u003ESample or development\u003C/th\u003E\n\u003Cth\u003EProduction\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPersistence\u003C/td\u003E\n\u003Ctd\u003EInMemory or local SQLite\u003C/td\u003E\n\u003Ctd\u003EDurable database\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeed accounts\u003C/td\u003E\n\u003Ctd\u003EExample users and passwords\u003C/td\u003E\n\u003Ctd\u003EReal provisioning\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDatabase initialization\u003C/td\u003E\n\u003Ctd\u003EMay reset sample data\u003C/td\u003E\n\u003Ctd\u003EReviewed migrations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHTTPS\u003C/td\u003E\n\u003Ctd\u003ELocal development certificate\u003C/td\u003E\n\u003Ctd\u003EProper TLS termination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECORS\u003C/td\u003E\n\u003Ctd\u003ELocalhost origins\u003C/td\u003E\n\u003Ctd\u003EExact trusted origins\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESigning keys\u003C/td\u003E\n\u003Ctd\u003EDevelopment defaults\u003C/td\u003E\n\u003Ctd\u003EManaged, persistent keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDiagnostics\u003C/td\u003E\n\u003Ctd\u003EDetailed debugging\u003C/td\u003E\n\u003Ctd\u003ERestrict sensitive diagnostics\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential recovery\u003C/td\u003E\n\u003Ctd\u003EDevelopment notifier\u003C/td\u003E\n\u003Ctd\u003EReal secure notification channel\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifier verification\u003C/td\u003E\n\u003Ctd\u003EDevelopment bypass\u003C/td\u003E\n\u003Ctd\u003EProof-based verification\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecrets\u003C/td\u003E\n\u003Ctd\u003ELocal settings\u003C/td\u003E\n\u003Ctd\u003ESecure configuration provider\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample projects include development-only implementations for credential reset notifications and identifier verification.\u003C/p\u003E\n\u003Cp\u003EThose implementations demonstrate extension points and should not be treated as production verification systems.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential recovery\u003C/h3\u003E\n\u003Cp\u003EIf your application exposes credential recovery, configure a real implementation of the credential reset notifier.\u003C/p\u003E\n\u003Cp\u003ERecovery secrets must be delivered through an appropriate channel and must not be exposed directly through anonymous API responses.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-verification\u0022\u003EIdentifier verification\u003C/h3\u003E\n\u003Cp\u003EIf your application supports verified email addresses, phone numbers, or other identifiers, use an implementation that verifies ownership through suitable proof.\u003C/p\u003E\n\u003Cp\u003EDo not use a development bypass verifier in production.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022signing-keys\u0022\u003ESigning keys\u003C/h3\u003E\n\u003Cp\u003EConfigure persistent signing keys for authentication modes that issue JWTs.\u003C/p\u003E\n\u003Cp\u003EDevelopment signing key providers are not a substitute for production key management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022deployment-checklist\u0022\u003E14. Deployment Checklist\u003C/h2\u003E\n\u003Cp\u003EBefore deploying, verify the following:\u003C/p\u003E\n\u003Cul class=\u0022contains-task-list\u0022\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E The selected architecture matches the actual client hosting model.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E The authentication server uses appropriate persistent storage.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Database migrations have been reviewed and applied.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Sample users and development-only verification services are disabled.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Production signing keys and secrets are configured.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E HTTPS is enforced at the appropriate boundary.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E UAuthHub and Resource API origins are explicitly configured.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Authentication endpoints and application endpoints are protected appropriately.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Client and server endpoint URLs match the deployment.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Session expiration, refresh, logout, and revocation have been tested.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Protected resources enforce server-side authorization.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Reverse proxy headers are configured correctly if applicable.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Authentication state and persistence survive the expected application lifecycle.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-your-architecture\u0022\u003E15. Choosing Your Architecture\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EStart with one server and one client. Connect more clients whenever you need them.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EUltimateAuth is built around two fundamental responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EUltimateAuth Server\u003C/strong\u003E \u2014 Manages authentication, users, credentials, sessions, authorization, and the security lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUltimateAuth Client\u003C/strong\u003E \u2014 Integrates an application with the authentication system using the appropriate client profile and authentication flows.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEvery application needs access to an authentication server, but that server does not have to be a separate application. It can be hosted within your existing ASP.NET Core application or deployed independently as UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-application-the-simplest-setup\u0022\u003ESingle Application \u2014 The Simplest Setup\u003C/h3\u003E\n\u003Cp\u003EFor a traditional ASP.NET Core or Blazor Server application, UltimateAuth Server and Client can run together.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Your Application \u2502\n\u2502 \u2502\n\u2502 UltimateAuth Client \u2502\n\u2502 \u2502 \u2502\n\u2502 UltimateAuth Server \u2502\n\u2502 \u2502 \u2502\n\u2502 Database \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis approach is ideal when you want authentication without introducing another service or deployment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022multiple-applications-one-authentication-server\u0022\u003EMultiple Applications \u2014 One Authentication Server\u003C/h3\u003E\n\u003Cp\u003EAs your system grows, you can connect multiple client applications to the same UltimateAuth Server.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Blazor Server Blazor WASM\n \u2502 \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u2502 \u2502 \u2502\n Razor Pages MAUI Other .NET Clients\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n UltimateAuth Server\n UAuthHub\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC \u25BC\n Users Sessions Authorization\n \u2502\n \u25BC\n Persistence\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach client can have its own application structure, hosting model, and authentication requirements while sharing the same authentication authority.\u003C/p\u003E\n\u003Cp\u003EFor example, a business platform might use Blazor Server for administration, Blazor WASM for its customer portal, MAUI for mobile access, and Razor Pages for an internal application.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdding another client does not require replacing the existing clients or creating another UltimateAuth Server.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe same users, credentials, security policies, and session infrastructure can serve multiple applications, subject to the authentication modes and client integrations supported by the installed version.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022distributed-applications-separate-resource-apis\u0022\u003EDistributed Applications \u2014 Separate Resource APIs\u003C/h3\u003E\n\u003Cp\u003EWhen your architecture includes independently deployed backend services, Resource APIs can integrate with the same authentication authority.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Multiple Client Applications\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n UAuthHub Resource APIs\n \u25B2 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Authentication\n Validation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EResource APIs protect application resources without becoming additional authentication servers.\u003C/p\u003E\n\u003Cp\u003EThis lets you separate business services from authentication infrastructure while maintaining a shared security model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022one-foundation-different-scales\u0022\u003EOne Foundation, Different Scales\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EYour scenario\u003C/th\u003E\n\u003Cth\u003ERecommended approach\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA simple Blazor Server application\u003C/td\u003E\n\u003Ctd\u003EIntegrated Server \u002B Client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA traditional ASP.NET Core application\u003C/td\u003E\n\u003Ctd\u003EServer hosted within the application\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA standalone Blazor WASM application\u003C/td\u003E\n\u003Ctd\u003EClient \u002B remote UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn Interactive WASM application\u003C/td\u003E\n\u003Ctd\u003EHost \u002B WASM Client \u002B remote UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple web, desktop, or mobile applications\u003C/td\u003E\n\u003Ctd\u003EMultiple Clients \u002B shared UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple backend services\u003C/td\u003E\n\u003Ctd\u003EShared UAuthHub \u002B Resource APIs\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUltimateAuth does not require a complex architecture to get started, and it does not restrict you to a single client as your application grows.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EStart with the simplest deployment that meets your needs. Add new client applications, persistent storage, or separate Resource APIs as your requirements evolve.\u003C/p\u003E\n\u003Cp\u003EThe authentication foundation remains consistent.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E \u2014 Build the minimal in-memory application.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E \u2014 Explore packages and supported integrations.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure server authentication and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure client profiles, endpoints, refresh, and reauthentication.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E \u2014 Explore effective options, dependency injection, and extension points.\u003C/li\u003E\n\u003C/ul\u003E\n",
+ "Html": "\n\u003Cp\u003E\u003Cstrong\u003EOne authentication framework. Different deployment models.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe \u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E demonstrates UltimateAuth with Blazor Server and in-memory storage.\u003C/p\u003E\n\u003Cp\u003EReal applications may require persistent data, a separate authentication server, browser-based clients, or independently deployed APIs.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports these scenarios through different service registrations and hosting integrations. You can choose the architecture that fits your application without configuring every available component.\u003C/p\u003E\n\u003Cp\u003EThis guide covers the setup patterns demonstrated by the UltimateAuth samples.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choose-your-architecture\u0022\u003E1. Choose Your Architecture\u003C/h2\u003E\n\u003Cp\u003EStart by identifying where authentication is hosted and how your application communicates with it.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication architecture\u003C/th\u003E\n\u003Cth\u003EAuthentication host\u003C/th\u003E\n\u003Cth\u003EApplication registration\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server, single project\u003C/td\u003E\n\u003Ctd\u003ESame application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B persistence \u002B Blazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server with EF Core\u003C/td\u003E\n\u003Ctd\u003ESame application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B EF Core \u002B Blazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDedicated UAuthHub\u003C/td\u003E\n\u003Ctd\u003ESeparate authentication application\u003C/td\u003E\n\u003Ctd\u003EServer \u002B persistence \u002B Hub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStandalone Blazor WASM\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EBlazor client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Web App, Interactive WASM\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EHost integration \u002B WASM client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeparate Resource API\u003C/td\u003E\n\u003Ctd\u003ERemote UAuthHub\u003C/td\u003E\n\u003Ctd\u003EResource API integration\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese models can be combined.\u003C/p\u003E\n\u003Cp\u003EFor example, an application might use an EF Core-backed UAuthHub, a standalone Blazor WASM frontend, and a separate Resource API.\u003C/p\u003E\n\u003Cp\u003EThe frontend handles user interaction, UAuthHub manages authentication, and the Resource API enforces access to protected resources.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistent-storage-with-entity-framework-core\u0022\u003E2. Persistent Storage with Entity Framework Core\u003C/h2\u003E\n\u003Cp\u003EFor applications that need authentication state to survive restarts, use a persistent storage provider.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides an Entity Framework Core bundle that registers its reference domain implementations and EF Core-backed stores.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-packages\u0022\u003EInstall packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\ndotnet add package Microsoft.EntityFrameworkCore.Design\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe SQLite provider is used here for simplicity. You can use another compatible EF Core database provider.\u003C/p\u003E\n\u003Cp\u003EWhen using the EF Core bundle, you do not need the in-memory bundle.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022register-the-services\u0022\u003ERegister the services\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing CodeBeam.UltimateAuth.Server.Extensions;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;, sqlite =\u0026gt; sqlite.MigrationsAssembly(typeof(Program).Assembly.GetName().Name!));\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthEntityFrameworkCore()\u003C/code\u003E registers the EF Core stores and reference domain implementations.\u003C/p\u003E\n\u003Cp\u003EThe bundle provides a unified \u003Ccode\u003EUAuthDbContext\u003C/code\u003E for the supported authentication domains.\u003C/p\u003E\n\u003Cp\u003EYou do not need to register each user, session, credential, token, or authorization store individually.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-database-connection\u0022\u003EConfigure the database connection\u003C/h3\u003E\n\u003Cp\u003EFor a deployed application, prefer configuration over hardcoded connection strings.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003Eappsettings.json\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EJSON\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-json\u0022\u003E{\n \u0026quot;ConnectionStrings\u0026quot;: {\n \u0026quot;UltimateAuth\u0026quot;: \u0026quot;Data Source=uauth.db\u0026quot;\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar connectionString = builder.Configuration.GetConnectionString(\u0026quot;UltimateAuth\u0026quot;)\n ?? throw new InvalidOperationException(\u0026quot;UltimateAuth connection string is missing.\u0026quot;);\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(connectionString);\n });\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor production databases, use your deployment environment\u0027s secret management facilities rather than storing credentials in source control.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022database-migrations\u0022\u003EDatabase migrations\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth does not automatically create or apply migrations when the EF Core bundle is registered.\u003C/p\u003E\n\u003Cp\u003EYour application owns the database migration lifecycle.\u003C/p\u003E\n\u003Cp\u003EInstall the EF Core CLI if necessary:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet tool install --global dotnet-ef\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ECreate the initial migration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef migrations add InitUAuth --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EApply it:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet ef database update --context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EFor Visual Studio Package Manager Console:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EPOWERSHELL\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-powershell\u0022\u003EAdd-Migration InitUAuth -Context UAuthDbContext\nUpdate-Database -Context UAuthDbContext\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EInstall \u003Ccode\u003EMicrosoft.EntityFrameworkCore.Tools\u003C/code\u003E if required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022applying-migrations-programmatically\u0022\u003EApplying migrations programmatically\u003C/h3\u003E\n\u003Cp\u003EThe EF Core samples demonstrate initialization through a scoped \u003Ccode\u003EUAuthDbContext\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor a controlled development environment:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\nusing (var scope = app.Services.CreateScope())\n{\n var db = scope.ServiceProvider.GetRequiredService\u0026lt;UAuthDbContext\u0026gt;();\n await db.Database.MigrateAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis applies pending migrations. It does not generate migration files.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EProduction guidance:\u003C/strong\u003E Prefer applying reviewed migrations as a separate deployment step, particularly when multiple application instances share a database.\u003C/p\u003E\n\u003Cp\u003EDo not copy the sample initializer\u0027s \u003Ccode\u003Ereset: true\u003C/code\u003E behavior into production. The samples use database reset functionality for repeatable demonstrations, not as a safe deployment strategy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022optional-sample-accounts-with-ef-core\u0022\u003EOptional: sample accounts with EF Core\u003C/h3\u003E\n\u003Cp\u003EThe sample seed package can also work with EF Core persistence.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Sample.Seed\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the scoped seed services:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScopedUltimateAuthSampleSeed();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAfter preparing the database:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eif (app.Environment.IsDevelopment())\n{\n await app.SeedUltimateAuthAsync();\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe difference matters:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EAddUltimateAuthSampleSeed()\u003C/code\u003E is used by the in-memory samples.\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EAddScopedUltimateAuthSampleSeed()\u003C/code\u003E is used by the EF Core samples.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EDo not deploy the demonstration accounts and passwords to production.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-integrated-authentication\u0022\u003E3. Blazor Server \u2014 Integrated Authentication\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A Blazor Server application that wants to host its own authentication system.\u003C/p\u003E\n\u003Cp\u003EThis is the simplest deployment model for a server-side Blazor application.\u003C/p\u003E\n\u003Cp\u003EThe application hosts UltimateAuth Server and uses the Blazor client adapter in the same project.\u003C/p\u003E\n\u003Cp\u003EA separate UAuthHub is not required.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022packages\u0022\u003EPackages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.EntityFrameworkCore.Bundle\ndotnet add package Microsoft.EntityFrameworkCore.Sqlite\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022services\u0022\u003EServices\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing CodeBeam.UltimateAuth.Server.Extensions;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services\n .AddRazorComponents()\n .AddInteractiveServerComponents();\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauth.db\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022pipeline\u0022\u003EPipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseHttpsRedirection();\napp.UseStaticFiles();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\n\napp.MapUltimateAuthEndpoints();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EAlso configure the \u003Ccode\u003EUAuthApp\u003C/code\u003E root component, login page, and UltimateAuth JavaScript file as demonstrated in QuickStart.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-behavior\u0022\u003EAuthentication behavior\u003C/h3\u003E\n\u003Cp\u003EThe default client profile resolution selects \u003Ccode\u003EPureOpaque\u003C/code\u003E for Blazor Server.\u003C/p\u003E\n\u003Cp\u003EThis uses session-oriented authentication rather than requiring the application to manage access and refresh tokens manually.\u003C/p\u003E\n\u003Cp\u003EYou can configure session lifetimes, login policies, credential policies, and other behavior through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Session.Lifetime = TimeSpan.FromDays(7);\n options.Identifiers.Behavior.AllowMultipleUsernames = true;\n options.Password.Level = PasswordPolicyLevel.Strict;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective authentication mode is resolved at runtime. The application does not need to select a single mode globally.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-security-configuration\u0022\u003EPassword Security Configuration\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth uses the Standard password policy by default. Applications can select Basic or Strict, or override individual requirements through \u003Ccode\u003Eoptions.Password\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor the preset comparison, configuration examples, and password-history settings, see \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022uauthhub-dedicated-authentication-server\u0022\u003E4. UAuthHub \u2014 Dedicated Authentication Server\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E Applications where multiple clients share one authentication authority.\u003C/p\u003E\n\u003Cp\u003EUAuthHub hosts UltimateAuth\u0027s authentication services separately from your frontend applications.\u003C/p\u003E\n\u003Cp\u003EIt is particularly relevant for standalone WASM applications, Interactive WASM applications, and systems with separate Resource APIs.\u003C/p\u003E\n\u003Cp\u003EA typical architecture looks like this:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Standalone WASM Interactive WASM\n \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n UAuthHub\n Authentication\n Sessions \u0026amp; Users\n \u2502\n \u25BC\n Persistence\n\n Resource API \u2500\u2500\u2500\u25BA UAuthHub\n (validation)\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022create-the-hub-application\u0022\u003ECreate the Hub application\u003C/h3\u003E\n\u003Cp\u003EA UAuthHub can be hosted in an ASP.NET Core Blazor Server application.\u003C/p\u003E\n\u003Cp\u003EThe current samples use Blazor Server to provide the Hub\u0027s login UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022in-memory-uauthhub\u0022\u003EIn-memory UAuthHub\u003C/h3\u003E\n\u003Cp\u003EFor development:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.InMemory.Bundle\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.InMemory;\nusing CodeBeam.UltimateAuth.Server.Extensions;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthInMemory()\n .AddUAuthHub(options =\u0026gt;\n {\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6130\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub\u0027s Blazor client registration is needed when the Hub itself hosts UltimateAuth-powered Blazor pages and login UI.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022ef-core-uauthhub\u0022\u003EEF Core UAuthHub\u003C/h3\u003E\n\u003Cp\u003EFor persistent storage:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.EntityFrameworkCore;\nusing Microsoft.EntityFrameworkCore;\n\nbuilder.Services.AddUltimateAuthServer()\n .AddUltimateAuthEntityFrameworkCore(db =\u0026gt;\n {\n db.UseSqlite(\u0026quot;Data Source=uauthhub.db\u0026quot;);\n })\n .AddUAuthHub(options =\u0026gt;\n {\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n });\n\nbuilder.Services.AddUltimateAuthClientBlazor();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe \u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E collection identifies browser origins permitted to interact with the Hub.\u003C/p\u003E\n\u003Cp\u003EUse exact origins, including scheme and port. Do not include paths such as \u003Ccode\u003E/home\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022hub-pipeline\u0022\u003EHub pipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseHttpsRedirection();\n\napp.UseUltimateAuthWithAspNetCore();\napp.UseAntiforgery();\n\napp.MapUltimateAuthEndpoints();\napp.MapUAuthHub();\n\napp.MapStaticAssets();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveServerRenderMode()\n .AddUltimateAuthRoutes(UAuthAssemblies.BlazorClient());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUltimateAuthEndpoints()\u003C/code\u003E maps the authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EMapUAuthHub()\u003C/code\u003E maps the Hub-specific entry endpoint.\u003C/p\u003E\n\u003Cp\u003EBoth registrations are used by the current UAuthHub samples.\u003C/p\u003E\n\u003Cp\u003EThe Hub also needs its login page and Blazor application lifecycle components.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-origins\u0022\u003EChoosing origins\u003C/h3\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EApplication\u003C/th\u003E\n\u003Cth\u003EURL\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUAuthHub\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6112\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EStandalone WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6130\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EInteractive WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003Ehttps://localhost:6132\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EConfigure the Hub\u0027s allowed origins for the actual frontend applications it serves.\u003C/p\u003E\n\u003Cp\u003EThese localhost addresses are illustrative and must match your deployment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-standalone-webassembly\u0022\u003E5. Blazor Standalone WebAssembly\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A browser-hosted application with a separate UAuthHub.\u003C/p\u003E\n\u003Cp\u003EStandalone Blazor WASM runs in the browser. It does not host UltimateAuth Server or database persistence.\u003C/p\u003E\n\u003Cp\u003EAuthentication is handled by the remote UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-packages\u0022\u003EClient packages\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services\u0022\u003EConfigure services\u003C/h3\u003E\n\u003Cp\u003EIn the standalone WASM application\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.RootComponents.Add\u0026lt;App\u0026gt;(\u0026quot;#app\u0026quot;);\nbuilder.RootComponents.Add\u0026lt;HeadOutlet\u0026gt;(\u0026quot;head::after\u0026quot;);\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6110/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6130/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nawait builder.Build().RunAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe current standalone WASM sample explicitly configures \u003Ccode\u003ELogin.AllowCredentialPost\u003C/code\u003E and the PKCE return URL.\u003C/p\u003E\n\u003Cp\u003EThese are client-side settings. The UAuthHub must be configured independently.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022what-the-urls-mean\u0022\u003EWhat the URLs mean\u003C/h3\u003E\n\u003Cp\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E points to the remote authentication endpoints.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EPkce.ReturnUrl\u003C/code\u003E identifies the frontend destination used by the relevant PKCE flow.\u003C/p\u003E\n\u003Cp\u003EThe Hub must recognize the frontend\u0027s origin, and any return URL used in an authentication flow must be validated against the application\u0027s security policy.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022root-component\u0022\u003ERoot component\u003C/h3\u003E\n\u003Cp\u003EA standalone WASM application should initialize UltimateAuth through \u003Ccode\u003EUAuthApp\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EFor example, its root application or routing component can contain:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client.Blazor\n\n\u0026lt;UAuthApp UseBuiltInRouter=\u0026quot;true\u0026quot; AppAssembly=\u0026quot;typeof(Program).Assembly\u0026quot; DefaultLayout=\u0026quot;typeof(Layout.MainLayout)\u0026quot; /\u0026gt;\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EInclude the UltimateAuth JavaScript asset in the application\u0027s HTML host page.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes\u0022\u003EAuthentication modes\u003C/h3\u003E\n\u003Cp\u003EThe default \u003Ccode\u003EBlazorWasm\u003C/code\u003E profile resolves to \u003Ccode\u003EHybrid\u003C/code\u003E in the current mode resolver.\u003C/p\u003E\n\u003Cp\u003EDo not manually force \u003Ccode\u003EPureOpaque\u003C/code\u003E for a standalone browser client simply because a Blazor Server application uses it.\u003C/p\u003E\n\u003Cp\u003EThe transport and authentication lifecycle must match the client environment.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-web-app-interactive-webassembly\u0022\u003E6. Blazor Web App \u2014 Interactive WebAssembly\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E A Blazor Web App with an ASP.NET Core host and a separate \u003Ccode\u003E.Client\u003C/code\u003E project.\u003C/p\u003E\n\u003Cp\u003EThis scenario differs from standalone WASM because components may participate in both server-side rendering and browser-side interactivity.\u003C/p\u003E\n\u003Cp\u003EThe current UltimateAuth sample registers the Blazor client integration in \u003Cstrong\u003Eboth projects\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EIt also adds a separate ASP.NET Core compatibility registration in the host.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022project-structure\u0022\u003EProject structure\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EMyApplication/\n\u251C\u2500\u2500 MyApplication/\n\u2502 \u251C\u2500\u2500 Program.cs\n\u2502 \u2514\u2500\u2500 Components/\n\u2502 \u2514\u2500\u2500 App.razor\n\u2502\n\u2514\u2500\u2500 MyApplication.Client/\n \u251C\u2500\u2500 Program.cs\n \u2514\u2500\u2500 Pages/\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host serves the application.\u003C/p\u003E\n\u003Cp\u003EThe \u003Ccode\u003E.Client\u003C/code\u003E project contains browser-executed components and services.\u003C/p\u003E\n\u003Cp\u003EA remote UAuthHub manages authentication.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-packages\u0022\u003E6.1 Host project packages\u003C/h3\u003E\n\u003Cp\u003EInstall the Blazor client integration and ASP.NET Core compatibility package in the host:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\ndotnet add package CodeBeam.UltimateAuth.Client.AspNetCore\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-services\u0022\u003E6.2 Host project services\u003C/h3\u003E\n\u003Cp\u003EIn the host \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.AspNetCore;\nusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nbuilder.Services\n .AddRazorComponents()\n .AddInteractiveWebAssemblyComponents();\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nbuilder.Services.AddUltimateAuthAspNetCoreCompatibility();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAddUltimateAuthAspNetCoreCompatibility()\u003C/code\u003E registers the compatibility authentication scheme and ASP.NET Core authorization services used by this hosting pattern.\u003C/p\u003E\n\u003Cp\u003EIt does \u003Cstrong\u003Enot\u003C/strong\u003E turn the host into an UltimateAuth authentication server.\u003C/p\u003E\n\u003Cp\u003EIt is also not equivalent to \u003Ccode\u003EAddUltimateAuthResourceApi()\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022host-project-pipeline\u0022\u003E6.3 Host project pipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseHttpsRedirection();\n\napp.UseAntiforgery();\n\napp.MapStaticAssets();\n\napp.MapRazorComponents\u0026lt;App\u0026gt;()\n .AddInteractiveWebAssemblyRenderMode()\n .AddAdditionalAssemblies(typeof(MyApplication.Client._Imports).Assembly, UAuthAssemblies.BlazorClient().First());\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EReplace \u003Ccode\u003EMyApplication.Client._Imports\u003C/code\u003E with the corresponding type from your client project.\u003C/p\u003E\n\u003Cp\u003EThe additional assemblies allow the host to discover routable components from the WASM client project and UltimateAuth\u0027s Blazor client assembly.\u003C/p\u003E\n\u003Cp\u003EThe sample does not register \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E in this host because authentication is delegated to a separate UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-project-packages\u0022\u003E6.4 Client project packages\u003C/h3\u003E\n\u003Cp\u003EInstall the Blazor client adapter in the \u003Ccode\u003E.Client\u003C/code\u003E project:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Client.Blazor\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-project-services\u0022\u003E6.5 Client project services\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EMyApplication.Client/Program.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client.Blazor.Extensions;\nusing CodeBeam.UltimateAuth.Core.Domain;\n\nvar builder = WebAssemblyHostBuilder.CreateDefault(args);\n\nbuilder.Services.AddScoped(sp =\u0026gt; new HttpClient\n{\n BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)\n});\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Pkce.ReturnUrl = \u0026quot;https://localhost:6132/home\u0026quot;;\n options.Login.AllowCredentialPost = true;\n options.Reauth.Behavior = ReauthBehavior.RaiseEvent;\n});\n\nawait builder.Build().RunAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe host and client configurations should agree on the remote UAuthHub URL and authentication flow settings.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022why-both-registrations-are-necessary\u0022\u003EWhy both registrations are necessary\u003C/h3\u003E\n\u003Cp\u003EThe host and browser client have separate dependency injection containers.\u003C/p\u003E\n\u003Cp\u003ERegistering \u003Ccode\u003EAddUltimateAuthClientBlazor()\u003C/code\u003E in the host does not automatically register those services inside the WebAssembly runtime.\u003C/p\u003E\n\u003Cp\u003ELikewise, registering the client adapter only in the \u003Ccode\u003E.Client\u003C/code\u003E project does not supply the services required during server-side integration.\u003C/p\u003E\n\u003Cp\u003EThe additional ASP.NET Core compatibility registration addresses the host\u0027s ASP.NET Core authentication and authorization integration requirements.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E This compatibility layer is not a replacement for server-side authorization of your protected business APIs.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-separate-protected-backend\u0022\u003E7. Resource API \u2014 Separate Protected Backend\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EBest for:\u003C/strong\u003E An API that needs UltimateAuth authentication validation but does not host the authentication system itself.\u003C/p\u003E\n\u003Cp\u003EA Resource API can be an ASP.NET Core Web API, MVC application, or another supported server-side application.\u003C/p\u003E\n\u003Cp\u003EIt does not need to be a Blazor application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022architecture\u0022\u003EArchitecture\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Blazor Client\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n UAuthHub Resource API\n \u25B2 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Authentication\n validation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Resource API uses the configured UAuthHub for remote authentication and session validation behavior.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022install-the-server-integration-package\u0022\u003EInstall the server integration package\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EBash\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-bash\u0022\u003Edotnet add package CodeBeam.UltimateAuth.Server\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-services-1\u0022\u003EConfigure services\u003C/h3\u003E\n\u003Cp\u003EIn the Resource API\u0027s \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Server.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.AddControllers();\n\nbuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://localhost:6112\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n options.CorsPolicyName = \u0026quot;resourceApi\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUAuthHubBaseUrl\u003C/code\u003E is the base address of the authentication server.\u003C/p\u003E\n\u003Cp\u003ENotice that this example uses the Hub origin without the \u003Ccode\u003E/auth\u003C/code\u003E suffix, matching the current Resource API samples.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedClientOrigins\u003C/code\u003E controls the browser origins permitted by the Resource API\u0027s CORS configuration.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-pipeline\u0022\u003EConfigure the pipeline\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar app = builder.Build();\n\napp.UseHttpsRedirection();\n\napp.UseUltimateAuthResourceApiWithAspNetCore();\n\napp.MapControllers();\n\napp.Run();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EUseUltimateAuthResourceApiWithAspNetCore()\u003C/code\u003E installs the Resource API integration middleware together with ASP.NET Core authentication and authorization middleware.\u003C/p\u003E\n\u003Cp\u003EYou do not need to register a local UltimateAuth Server or its persistence bundle in the Resource API.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022protecting-endpoints\u0022\u003EProtecting endpoints\u003C/h3\u003E\n\u003Cp\u003EUse ASP.NET Core authorization attributes for endpoints that require authentication:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing Microsoft.AspNetCore.Authorization;\nusing Microsoft.AspNetCore.Mvc;\n\n[ApiController]\n[Route(\u0026quot;api/products\u0026quot;)]\npublic class ProductsController : ControllerBase\n{\n [HttpGet]\n [Authorize]\n public IActionResult GetProducts()\n {\n return Ok(new[]\n {\n new { Id = 1, Name = \u0026quot;Example product\u0026quot; }\n });\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe authorization attribute is enforced by ASP.NET Core on the server.\u003C/p\u003E\n\u003Cp\u003EFor permission-specific rules, configure and apply the corresponding authorization policies.\u003C/p\u003E\n\u003Cp\u003EDo not rely on Blazor UI authorization checks to protect Resource API endpoints.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022cors-configuration\u0022\u003ECORS configuration\u003C/h3\u003E\n\u003Cp\u003EThe current Resource API integration registers CORS from the configured allowed origins.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eoptions.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnly add origins that should be allowed to call your API from a browser.\u003C/p\u003E\n\u003Cp\u003ECORS is a browser access control mechanism, not an authentication or authorization mechanism.\u003C/p\u003E\n\u003Cp\u003EA request must still pass the applicable authentication and authorization checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-vs-uauthhub\u0022\u003EResource API vs UAuthHub\u003C/h3\u003E\n\u003Cp\u003EThese are different responsibilities.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EUAuthHub\u003C/th\u003E\n\u003Cth\u003EResource API\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHosts authentication flows\u003C/td\u003E\n\u003Ctd\u003EHosts application resources\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EManages authentication sessions\u003C/td\u003E\n\u003Ctd\u003EValidates remote authentication context\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUses authentication persistence\u003C/td\u003E\n\u003Ctd\u003EDoes not require local authentication stores\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EProvides login and related endpoints\u003C/td\u003E\n\u003Ctd\u003EProvides application endpoints\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EServes authentication clients\u003C/td\u003E\n\u003Ctd\u003EServes authorized API consumers\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EUse a Resource API when you want independent deployment of business endpoints without duplicating the authentication server.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022resource-api-with-an-ef-core-backed-uauthhub\u0022\u003E8. Resource API with an EF Core-Backed UAuthHub\u003C/h2\u003E\n\u003Cp\u003EThe Resource API does not need to use EF Core just because UAuthHub does.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003EInteractive WASM\n \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25BA Resource API\n \u2502\n \u25BC\n UAuthHub\n \u2502\n \u25BC\n EF Core Database\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Hub stores users, credentials, sessions, and related security state in its database.\u003C/p\u003E\n\u003Cp\u003EThe Resource API communicates with the Hub through the configured remote validation integration.\u003C/p\u003E\n\u003Cp\u003EIts registration remains:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthResourceApi(options =\u0026gt;\n{\n options.UAuthHubBaseUrl = \u0026quot;https://localhost:6112\u0026quot;;\n options.AllowedClientOrigins.Add(\u0026quot;https://localhost:6132\u0026quot;);\n options.CorsPolicyName = \u0026quot;resourceApi\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis separation allows the API and authentication infrastructure to be deployed, scaled, and maintained independently.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022calling-a-resource-api-from-blazor\u0022\u003E9. Calling a Resource API from Blazor\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth provides a unified Resource Client for accessing protected application APIs.\u003C/p\u003E\n\u003Cp\u003EInstead of configuring a separate HTTP client and implementing browser credential handling for ordinary resource requests, applications can use \u003Ccode\u003EIUAuthClient.Resource\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThis keeps authentication, identity operations, and application resource requests accessible through the same client SDK.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configure-the-resource-api\u0022\u003EConfigure the Resource API\u003C/h3\u003E\n\u003Cp\u003EFor a separate Resource API, configure its origin through the Blazor client registration:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client;\n\nbuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n\n options.Resource.BaseAddress = \u0026quot;https://localhost:6122\u0026quot;;\n options.Resource.Credentials = UAuthResourceCredentialsMode.Auto;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EEndpoints.BasePath\u003C/code\u003E identifies the authentication server.\u003C/p\u003E\n\u003Cp\u003E\u003Ccode\u003EResource.BaseAddress\u003C/code\u003E identifies the application Resource API.\u003C/p\u003E\n\u003Cp\u003EThese addresses can point to different applications.\u003C/p\u003E\n\u003Cp\u003EIf the Resource API shares the application\u0027s origin, \u003Ccode\u003EResource.BaseAddress\u003C/code\u003E can remain \u003Ccode\u003Enull\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022call-protected-resources\u0022\u003ECall Protected Resources\u003C/h3\u003E\n\u003Cp\u003EInject \u003Ccode\u003EIUAuthClient\u003C/code\u003E into a Blazor component:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003ERazor\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-razor\u0022\u003E@using CodeBeam.UltimateAuth.Client\n@inject IUAuthClient UAuthClient\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen retrieve application data:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar result = await UAuthClient.Resource.GetAsync\u0026lt;List\u0026lt;Product\u0026gt;\u0026gt;(\u0026quot;/api/products\u0026quot;);\n\nif (result.IsSuccess \u0026amp;\u0026amp; result.Value is not null)\n{\n products = result.Value;\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe Resource Client also supports POST, PUT, PATCH, and DELETE operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022application-specific-api-services\u0022\u003EApplication-Specific API Services\u003C/h3\u003E\n\u003Cp\u003EApplications can organize resource operations into small domain-specific services.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Client;\nusing CodeBeam.UltimateAuth.Core.Contracts;\n\npublic sealed class ProductApiService(IUAuthClient client)\n{\n public Task\u0026lt;UAuthResult\u0026lt;List\u0026lt;Product\u0026gt;\u0026gt;\u0026gt; GetAllAsync()\n =\u0026gt; client.Resource.GetAsync\u0026lt;List\u0026lt;Product\u0026gt;\u0026gt;(\u0026quot;/api/products\u0026quot;);\n\n public Task\u0026lt;UAuthResult\u0026lt;Product\u0026gt;\u0026gt; CreateAsync(Product product)\n =\u0026gt; client.Resource.PostAsync\u0026lt;Product\u0026gt;(\u0026quot;/api/products\u0026quot;,product);\n\n public Task\u0026lt;UAuthResult\u0026gt; DeleteAsync(int id)\n =\u0026gt; client.Resource.DeleteAsync($\u0026quot;/api/products/{id}\u0026quot;);\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003ERegister the application service:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddScoped\u0026lt;ProductApiService\u0026gt;();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe service uses UltimateAuth\u0027s transport and result contracts rather than implementing another HTTP abstraction.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022interactive-webassembly\u0022\u003EInteractive WebAssembly\u003C/h3\u003E\n\u003Cp\u003EIn an Interactive WebAssembly application, configure the UltimateAuth Blazor client in each project where the Resource Client will be used.\u003C/p\u003E\n\u003Cp\u003EThe host and \u003Ccode\u003E.Client\u003C/code\u003E project have separate dependency injection containers.\u003C/p\u003E\n\u003Cp\u003EA shared application API service may be registered in both projects when components execute in both environments.\u003C/p\u003E\n\u003Cp\u003EThe Resource Client uses browser-based transport, including when invoked from an interactive Blazor Server component.\u003C/p\u003E\n\u003Cp\u003EBrowser interop must be available before making resource requests. Do not initiate browser-dependent resource requests during static prerendering.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standalone-webassembly\u0022\u003EStandalone WebAssembly\u003C/h3\u003E\n\u003Cp\u003EStandalone Blazor WebAssembly applications use the same Resource Client API.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthClientBlazor(options =\u0026gt;\n{\n options.Endpoints.BasePath = \u0026quot;https://localhost:6112/auth\u0026quot;;\n options.Resource.BaseAddress = \u0026quot;https://localhost:6122\u0026quot;;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EOnce configured, application components can call protected resources through \u003Ccode\u003EIUAuthClient.Resource\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EA separate application-specific \u003Ccode\u003EHttpClient\u003C/code\u003E is not required for these requests.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022browser-credentials-and-cors\u0022\u003EBrowser Credentials and CORS\u003C/h3\u003E\n\u003Cp\u003EFor cross-origin requests, the browser and receiving API must agree on credential handling.\u003C/p\u003E\n\u003Cp\u003EThe Resource Client supports \u003Ccode\u003EAuto\u003C/code\u003E, \u003Ccode\u003ESameOrigin\u003C/code\u003E, \u003Ccode\u003EInclude\u003C/code\u003E, and \u003Ccode\u003EOmit\u003C/code\u003E credential policies.\u003C/p\u003E\n\u003Cp\u003EFor cross-origin cookie-based authentication, configure the appropriate credential policy and ensure that the Resource API supports the required CORS and cookie settings.\u003C/p\u003E\n\u003Cp\u003EThe Resource Client does not automatically make an API authenticated merely because the user has signed in through UAuthHub.\u003C/p\u003E\n\u003Cp\u003EThe receiving API must validate the authentication material associated with the configured authentication mode and enforce its authorization policies.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022blazor-server-and-direct-service-calls\u0022\u003EBlazor Server and Direct Service Calls\u003C/h3\u003E\n\u003Cp\u003EBlazor Server applications can often access application services directly when those services are hosted in the same process.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Evar products = await productService.GetAllAsync();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThere is no need to introduce an HTTP request solely to use the Resource Client.\u003C/p\u003E\n\u003Cp\u003EUse \u003Ccode\u003EIUAuthClient.Resource\u003C/code\u003E when the application actually needs to communicate with a protected Resource API through the browser.\u003C/p\u003E\n\u003Cp\u003EFor server-to-server requests, use an appropriate server-side HTTP client and authentication mechanism.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022learn-more\u0022\u003ELearn More\u003C/h3\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/client-sdk/resource-api-requests\u0022\u003EResource API Requests\u003C/a\u003E for supported operations, result handling, credential configuration, security considerations, and request cancellation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022authentication-modes-and-client-profiles\u0022\u003E10. Authentication Modes and Client Profiles\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates \u003Cstrong\u003Eclient profile detection\u003C/strong\u003E from \u003Cstrong\u003Eauthentication mode enforcement\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe default resolver maps client profiles to authentication modes.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EClient profile\u003C/th\u003E\n\u003Cth\u003EDefault resolved mode\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor Server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureOpaque\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EBlazor WASM\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EWeb Server\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMAUI\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EHybrid\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAPI\u003C/td\u003E\n\u003Ctd\u003E\u003Ccode\u003EPureJwt\u003C/code\u003E\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThis mapping describes the resolver\u0027s behavior, not a guarantee that every mode is currently fully supported by the server.\u003C/p\u003E\n\u003Cp\u003EIn particular, \u003Ccode\u003EPureJwt\u003C/code\u003E and \u003Ccode\u003ESemiHybrid\u003C/code\u003E have implementation limitations in the current release.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022restrict-allowed-modes\u0022\u003ERestrict allowed modes\u003C/h3\u003E\n\u003Cp\u003EOn the authentication server:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.AllowedModes =\n [\n UAuthMode.PureOpaque,\n UAuthMode.Hybrid\n ];\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003EAllowedModes\u003C/code\u003E is a guardrail.\u003C/p\u003E\n\u003Cp\u003EIt does not select a mode for every request.\u003C/p\u003E\n\u003Cp\u003EThe effective mode is resolved from the client profile and then checked against the server\u0027s allowed-mode policy.\u003C/p\u003E\n\u003Cp\u003EFor most applications, use the default mode resolution unless you have a specific integration requirement.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-options-for-real-applications\u0022\u003E11. Server Options for Real Applications\u003C/h2\u003E\n\u003Cp\u003EProduction configuration should reflect your application\u0027s security requirements.\u003C/p\u003E\n\u003Cp\u003EFor example:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Login.MaxFailedAttempts = 5;\n options.Login.LockoutDuration = TimeSpan.FromMinutes(15);\n options.Identifiers.Behavior.AllowMultipleUsernames = true;\n options.UserProfile.EnableMultiProfile = false;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EServer options also compose the Core-defined login, session, token, PKCE, and multi-tenant options.\u003C/p\u003E\n\u003Cp\u003EConfigure those through \u003Ccode\u003EAddUltimateAuthServer()\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EDo not independently configure Core options and assume the server will automatically use them.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuration-files\u0022\u003EConfiguration files\u003C/h3\u003E\n\u003Cp\u003EUltimateAuth also supports configuration through \u003Ccode\u003Eappsettings.json\u003C/code\u003E.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EJSON\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-json\u0022\u003E{\n \u0026quot;UltimateAuth\u0026quot;: {\n \u0026quot;Server\u0026quot;: {\n \u0026quot;Login\u0026quot;: {\n \u0026quot;MaxFailedAttempts\u0026quot;: 5,\n \u0026quot;LockoutDuration\u0026quot;: \u0026quot;00:15:00\u0026quot;\n }\n }\n }\n}\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe server options registration binds the \u003Ccode\u003EUltimateAuth:Server\u003C/code\u003E configuration section.\u003C/p\u003E\n\u003Cp\u003ERemember that effective mode defaults and mode-specific overrides are applied after the base options have been configured.\u003C/p\u003E\n\u003Cp\u003EFor detailed configuration behavior, see Server Options and Advanced Configuration.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022client-ip-address-reverse-proxy\u0022\u003E12. Client IP Address \u0026amp; Reverse Proxy\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth uses ASP.NET Core\u0027s resolved client IP address for device information and IP-based rate limiting. It does not directly trust \u003Ccode\u003EX-Forwarded-For\u003C/code\u003E or \u003Ccode\u003ECF-Connecting-IP\u003C/code\u003E headers.\u003C/p\u003E\n\u003Cp\u003EIf your application runs behind Caddy, Nginx, or another reverse proxy, configure ASP.NET Core to trust that proxy.\u003C/p\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E, before \u003Ccode\u003Ebuilder.Build()\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing System.Net;\nusing Microsoft.AspNetCore.HttpOverrides;\n\nbuilder.Services.Configure\u0026lt;ForwardedHeadersOptions\u0026gt;(options =\u0026gt;\n{\n options.ForwardedHeaders =\n ForwardedHeaders.XForwardedFor |\n ForwardedHeaders.XForwardedProto;\n\n // Replace with your actual Caddy or Nginx proxy IP.\n options.KnownProxies.Add(IPAddress.Parse(\u0026quot;172.20.0.5\u0026quot;));\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThen enable the middleware \u003Cstrong\u003Ebefore\u003C/strong\u003E UltimateAuth and HTTPS redirection:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eapp.UseForwardedHeaders();\n\napp.UseHttpsRedirection();\napp.UseUltimateAuthWithAspNetCore();\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe example IP \u003Ccode\u003E172.20.0.5\u003C/code\u003E represents a trusted reverse proxy, not the visitor\u0027s IP address. Replace it with your actual proxy IP. For Docker deployments, ensure the proxy address is stable or configure a narrowly scoped trusted network.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EImportant:\u003C/strong\u003E Never trust arbitrary forwarded headers or clear the trusted proxy lists to accept all sources. If your application connects directly to Kestrel without a reverse proxy, no forwarded headers configuration is required.\u003C/p\u003E\n\u003Cp\u003EFor multi-proxy deployments, configure the complete trusted proxy chain and forwarding behavior according to your infrastructure.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022development-and-production-differences\u0022\u003E13. Development and Production Differences\u003C/h2\u003E\n\u003Cp\u003EThe samples intentionally prioritize visibility and repeatability.\u003C/p\u003E\n\u003Cp\u003EA production application needs additional safeguards.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EArea\u003C/th\u003E\n\u003Cth\u003ESample or development\u003C/th\u003E\n\u003Cth\u003EProduction\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPersistence\u003C/td\u003E\n\u003Ctd\u003EInMemory or local SQLite\u003C/td\u003E\n\u003Ctd\u003EDurable database\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESeed accounts\u003C/td\u003E\n\u003Ctd\u003EExample users and passwords\u003C/td\u003E\n\u003Ctd\u003EReal provisioning\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDatabase initialization\u003C/td\u003E\n\u003Ctd\u003EMay reset sample data\u003C/td\u003E\n\u003Ctd\u003EReviewed migrations\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EHTTPS\u003C/td\u003E\n\u003Ctd\u003ELocal development certificate\u003C/td\u003E\n\u003Ctd\u003EProper TLS termination\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECORS\u003C/td\u003E\n\u003Ctd\u003ELocalhost origins\u003C/td\u003E\n\u003Ctd\u003EExact trusted origins\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESigning keys\u003C/td\u003E\n\u003Ctd\u003EDevelopment defaults\u003C/td\u003E\n\u003Ctd\u003EManaged, persistent keys\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDiagnostics\u003C/td\u003E\n\u003Ctd\u003EDetailed debugging\u003C/td\u003E\n\u003Ctd\u003ERestrict sensitive diagnostics\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECredential recovery\u003C/td\u003E\n\u003Ctd\u003EDevelopment notifier\u003C/td\u003E\n\u003Ctd\u003EReal secure notification channel\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EIdentifier verification\u003C/td\u003E\n\u003Ctd\u003EDevelopment bypass\u003C/td\u003E\n\u003Ctd\u003EProof-based verification\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ESecrets\u003C/td\u003E\n\u003Ctd\u003ELocal settings\u003C/td\u003E\n\u003Ctd\u003ESecure configuration provider\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe sample projects include development-only implementations for credential reset notifications and identifier verification.\u003C/p\u003E\n\u003Cp\u003EThose implementations demonstrate extension points and should not be treated as production verification systems.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential recovery\u003C/h3\u003E\n\u003Cp\u003EIf your application exposes credential recovery, configure a real implementation of the credential reset notifier.\u003C/p\u003E\n\u003Cp\u003ERecovery secrets must be delivered through an appropriate channel and must not be exposed directly through anonymous API responses.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022identifier-verification\u0022\u003EIdentifier verification\u003C/h3\u003E\n\u003Cp\u003EIf your application supports verified email addresses, phone numbers, or other identifiers, use an implementation that verifies ownership through suitable proof.\u003C/p\u003E\n\u003Cp\u003EDo not use a development bypass verifier in production.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022signing-keys\u0022\u003ESigning keys\u003C/h3\u003E\n\u003Cp\u003EConfigure persistent signing keys for authentication modes that issue JWTs.\u003C/p\u003E\n\u003Cp\u003EDevelopment signing key providers are not a substitute for production key management.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022deployment-checklist\u0022\u003E14. Deployment Checklist\u003C/h2\u003E\n\u003Cp\u003EBefore deploying, verify the following:\u003C/p\u003E\n\u003Cul class=\u0022contains-task-list\u0022\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E The selected architecture matches the actual client hosting model.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E The authentication server uses appropriate persistent storage.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Database migrations have been reviewed and applied.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Sample users and development-only verification services are disabled.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Production signing keys and secrets are configured.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E HTTPS is enforced at the appropriate boundary.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E UAuthHub and Resource API origins are explicitly configured.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Authentication endpoints and application endpoints are protected appropriately.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Client and server endpoint URLs match the deployment.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Session expiration, refresh, logout, and revocation have been tested.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Protected resources enforce server-side authorization.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Reverse proxy headers are configured correctly if applicable.\u003C/li\u003E\n\u003Cli class=\u0022task-list-item\u0022\u003E\u003Cinput disabled=\u0022disabled\u0022 type=\u0022checkbox\u0022\u003E Authentication state and persistence survive the expected application lifecycle.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022choosing-your-architecture\u0022\u003E15. Choosing Your Architecture\u003C/h2\u003E\n\u003Cp\u003E\u003Cstrong\u003EStart with one server and one client. Connect more clients whenever you need them.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EUltimateAuth is built around two fundamental responsibilities:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003EUltimateAuth Server\u003C/strong\u003E \u2014 Manages authentication, users, credentials, sessions, authorization, and the security lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Cstrong\u003EUltimateAuth Client\u003C/strong\u003E \u2014 Integrates an application with the authentication system using the appropriate client profile and authentication flows.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EEvery application needs access to an authentication server, but that server does not have to be a separate application. It can be hosted within your existing ASP.NET Core application or deployed independently as UAuthHub.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022single-application-the-simplest-setup\u0022\u003ESingle Application \u2014 The Simplest Setup\u003C/h3\u003E\n\u003Cp\u003EFor a traditional ASP.NET Core or Blazor Server application, UltimateAuth Server and Client can run together.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Your Application \u2502\n\u2502 \u2502\n\u2502 UltimateAuth Client \u2502\n\u2502 \u2502 \u2502\n\u2502 UltimateAuth Server \u2502\n\u2502 \u2502 \u2502\n\u2502 Database \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis approach is ideal when you want authentication without introducing another service or deployment.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022multiple-applications-one-authentication-server\u0022\u003EMultiple Applications \u2014 One Authentication Server\u003C/h3\u003E\n\u003Cp\u003EAs your system grows, you can connect multiple client applications to the same UltimateAuth Server.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Blazor Server Blazor WASM\n \u2502 \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u2502 \u2502 \u2502\n Razor Pages MAUI Other .NET Clients\n \u2502 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u25BC\n UltimateAuth Server\n UAuthHub\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC \u25BC\n Users Sessions Authorization\n \u2502\n \u25BC\n Persistence\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EEach client can have its own application structure, hosting model, and authentication requirements while sharing the same authentication authority.\u003C/p\u003E\n\u003Cp\u003EFor example, a business platform might use Blazor Server for administration, Blazor WASM for its customer portal, MAUI for mobile access, and Razor Pages for an internal application.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EAdding another client does not require replacing the existing clients or creating another UltimateAuth Server.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EThe same users, credentials, security policies, and session infrastructure can serve multiple applications, subject to the authentication modes and client integrations supported by the installed version.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022distributed-applications-separate-resource-apis\u0022\u003EDistributed Applications \u2014 Separate Resource APIs\u003C/h3\u003E\n\u003Cp\u003EWhen your architecture includes independently deployed backend services, Resource APIs can integrate with the same authentication authority.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EText\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-text\u0022\u003E Multiple Client Applications\n \u2502\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u25BC \u25BC\n UAuthHub Resource APIs\n \u25B2 \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Authentication\n Validation\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EResource APIs protect application resources without becoming additional authentication servers.\u003C/p\u003E\n\u003Cp\u003EThis lets you separate business services from authentication infrastructure while maintaining a shared security model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022one-foundation-different-scales\u0022\u003EOne Foundation, Different Scales\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EYour scenario\u003C/th\u003E\n\u003Cth\u003ERecommended approach\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA simple Blazor Server application\u003C/td\u003E\n\u003Ctd\u003EIntegrated Server \u002B Client\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA traditional ASP.NET Core application\u003C/td\u003E\n\u003Ctd\u003EServer hosted within the application\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EA standalone Blazor WASM application\u003C/td\u003E\n\u003Ctd\u003EClient \u002B remote UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAn Interactive WASM application\u003C/td\u003E\n\u003Ctd\u003EHost \u002B WASM Client \u002B remote UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple web, desktop, or mobile applications\u003C/td\u003E\n\u003Ctd\u003EMultiple Clients \u002B shared UAuthHub\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMultiple backend services\u003C/td\u003E\n\u003Ctd\u003EShared UAuthHub \u002B Resource APIs\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Cstrong\u003EUltimateAuth does not require a complex architecture to get started, and it does not restrict you to a single client as your application grows.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EStart with the simplest deployment that meets your needs. Add new client applications, persistent storage, or separate Resource APIs as your requirements evolve.\u003C/p\u003E\n\u003Cp\u003EThe authentication foundation remains consistent.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/quickstart\u0022\u003EQuickStart\u003C/a\u003E \u2014 Build the minimal in-memory application.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/ecosystem\u0022\u003EEcosystem\u003C/a\u003E \u2014 Explore packages and supported integrations.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/server-options\u0022\u003EServer Options\u003C/a\u003E \u2014 Configure server authentication and security behavior.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/client-options\u0022\u003EClient Options\u003C/a\u003E \u2014 Configure client profiles, endpoints, refresh, and reauthentication.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/configuration/advanced-configuration\u0022\u003EAdvanced Configuration\u003C/a\u003E \u2014 Explore effective options, dependency injection, and extension points.\u003C/li\u003E\n\u003C/ul\u003E\n",
"Headings": [
{
"Id": "choose-your-architecture",
@@ -68,6 +68,11 @@
"Text": "Authentication behavior",
"Level": 1
},
+ {
+ "Id": "password-security-configuration",
+ "Text": "Password Security Configuration",
+ "Level": 1
+ },
{
"Id": "uauthhub-dedicated-authentication-server",
"Text": "4. UAuthHub \u2014 Dedicated Authentication Server",
@@ -339,6 +344,6 @@
"Level": 0
}
],
- "SourceHash": "B1EA20A2729E11F13A9E2D7B12F6D1519AD7A45FE3DBFB4A9D79C01E06D0ED5A",
+ "SourceHash": "58DC0990FBB207C203C68D304A561D1ED8C87494BD4DEA86C1171568EA6B4466",
"BuilderVersion": "1"
}
\ No newline at end of file
diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json
index 5b33614a..7d5d0681 100644
--- a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json
+++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/plugin-domains/credentials.json
@@ -1,7 +1,7 @@
{
"Slug": "plugin-domains/credentials",
"Title": "Credentials",
- "Html": "\n\u003Cp\u003EAuthentication establishes whether an entity can be trusted to act as a particular identity. A credential provides the evidence used to establish that trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats credentials as a distinct domain rather than embedding authentication secrets directly into user records or authentication flows.\u003C/p\u003E\n\u003Cp\u003EThe \u003Cstrong\u003ECredentials Plugin Domain\u003C/strong\u003E manages the lifecycle, verification, and security of authentication credentials while allowing the authentication engine to remain independent of a particular credential type.\u003C/p\u003E\n\u003Cp\u003EThe current reference implementation supports password credentials. Its architecture establishes the separation of responsibilities needed to accommodate additional credential mechanisms without redefining the underlying authentication model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003EUnderstanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential represents authentication material or a mechanism through which an identity can demonstrate possession of an accepted authentication factor.\u003C/p\u003E\n\u003Cp\u003ECredentials may take different forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential mechanism\u003C/th\u003E\n\u003Cth\u003EAuthentication evidence\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPassword\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EKnowledge of a secret verified against a protected representation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOne-time code\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPossession of a time-limited or single-use authentication value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECryptographic credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProof of control over a private key or authenticator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExternal identity provider\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA trusted authentication assertion established through an external protocol\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms do not necessarily share the same storage, verification, or lifecycle requirements.\u003C/p\u003E\n\u003Cp\u003EFor example, a password requires secure secret verification, while a cryptographic credential may require signature validation and public-key metadata. An external provider may require validating protocol assertions rather than storing a reusable secret.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s credential architecture is intended to accommodate these differences through explicit credential-specific behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation:\u003C/strong\u003E Password credentials are supported. The other mechanisms illustrate the broader credential model and its intended extension boundaries; they are not presented as implemented credential types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-user-identity\u0022\u003ECredentials and User Identity\u003C/h2\u003E\n\u003Cp\u003EA user identity and a credential are separate concepts.\u003C/p\u003E\n\u003Cp\u003EThe Users domain manages identity ownership, identifiers, and account lifecycle. The Credentials domain manages the authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThis separation means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user can exist independently of a particular credential.\u003C/li\u003E\n\u003Cli\u003ECredential state can change without redefining the user\u0027s identity.\u003C/li\u003E\n\u003Cli\u003EAn identity can conceptually be associated with multiple authentication mechanisms.\u003C/li\u003E\n\u003Cli\u003ECredential verification does not replace user lifecycle or account eligibility checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA username or email address is generally an \u003Cstrong\u003Eidentifier\u003C/strong\u003E, not a credential.\u003C/p\u003E\n\u003Cp\u003EAn identifier helps resolve which identity is involved in an authentication attempt. A credential provides evidence that the actor is entitled to authenticate as that identity.\u003C/p\u003E\n\u003Cp\u003EFor a password-based login, these responsibilities work together:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe submitted identifier is resolved to an identity.\u003C/li\u003E\n\u003Cli\u003EThe applicable password credential is located.\u003C/li\u003E\n\u003Cli\u003EThe submitted secret is verified.\u003C/li\u003E\n\u003Cli\u003EAuthentication policies and relevant security conditions are evaluated.\u003C/li\u003E\n\u003Cli\u003EThe authentication engine establishes authentication state if the flow succeeds.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe credential domain participates in establishing identity proof, but it does not independently determine the entire authentication outcome.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022responsibilities-across-the-framework\u0022\u003EResponsibilities Across the Framework\u003C/h2\u003E\n\u003Cp\u003ECredential support spans several architectural layers, each with a different responsibility.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ELayer\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECore\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EFoundational authentication contracts and security concepts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EServer\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication flow execution, orchestration, and authentication-state management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Bridge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContracts connecting credential capabilities to the authentication server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Contracts\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EShared credential operation models and results\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Reference Implementation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential management, validation, recovery, and lifecycle behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPersistence\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EStorage of credential records and related security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction is between \u003Cstrong\u003Eusing credentials to authenticate\u003C/strong\u003E and \u003Cstrong\u003Emanaging credentials as domain resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe authentication engine coordinates authentication flows. The Credentials domain owns the rules governing credential records, their usability, and their lifecycle.\u003C/p\u003E\n\u003Cp\u003EAn application may integrate a custom credential system through the relevant authentication contracts without necessarily adopting the reference credential management implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-credentials\u0022\u003EPassword Credentials\u003C/h2\u003E\n\u003Cp\u003EPassword is the first supported credential mechanism in UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EA password credential represents a password-based authentication factor associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThe reference implementation separates the submitted secret from its protected representation and the credential\u0027s operational state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022secret-protection\u0022\u003ESecret Protection\u003C/h3\u003E\n\u003Cp\u003EPasswords must not be stored as plaintext or returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EThe password verification process operates against a protected password representation using the configured password hashing infrastructure.\u003C/p\u003E\n\u003Cp\u003ECredential management APIs should expose only the information required for their intended operations, not the underlying secret.\u003C/p\u003E\n\u003Cp\u003EThis separation also allows password verification and password management to evolve without changing the user identity model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-security-state\u0022\u003ECredential Security State\u003C/h3\u003E\n\u003Cp\u003EA credential is not necessarily usable merely because a matching secret has been supplied.\u003C/p\u003E\n\u003Cp\u003ECredential validation must also account for relevant lifecycle and security conditions, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the credential is active or revoked.\u003C/li\u003E\n\u003Cli\u003EWhether an applicable expiration condition has been reached.\u003C/li\u003E\n\u003Cli\u003EWhether the credential belongs to the intended identity.\u003C/li\u003E\n\u003Cli\u003EWhether the requested operation is permitted.\u003C/li\u003E\n\u003Cli\u003EWhether the credential is eligible for the authentication mechanism being executed.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESecret verification and credential usability are related but distinct decisions.\u003C/p\u003E\n\u003Cp\u003EA correctly supplied password must not make a revoked or otherwise unusable credential valid.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates credential-specific verification from the broader authentication flow.\u003C/p\u003E\n\u003Cp\u003EFor password authentication, credential validation includes evaluating the supplied secret against the stored protected representation and enforcing the relevant credential conditions.\u003C/p\u003E\n\u003Cp\u003EThe authentication server then uses the result within its authentication flow.\u003C/p\u003E\n\u003Cp\u003EThis boundary matters because credential verification is not equivalent to session creation or token issuance.\u003C/p\u003E\n\u003Cp\u003EA successful credential check establishes evidence for authentication. The authentication engine remains responsible for determining whether the flow can complete and what authentication state should be created.\u003C/p\u003E\n\u003Cp\u003EThe same architectural distinction applies to other credential mechanisms.\u003C/p\u003E\n\u003Cp\u003EA future cryptographic credential validator, for example, would verify a different kind of proof, but it would still participate in a broader authentication decision rather than independently issuing authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-lifecycle\u0022\u003ECredential Lifecycle\u003C/h2\u003E\n\u003Cp\u003ECredentials are managed resources with their own lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe reference domain provides operations around credential creation, secret changes, revocation, and recovery.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAdd\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERegister a credential for an identity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChange secret\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace authentication material through an authorized operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMake a credential unusable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EBegin reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInitiate a controlled credential recovery process\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EComplete reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace a secret after satisfying recovery requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese operations have different security requirements.\u003C/p\u003E\n\u003Cp\u003EChanging a password while authenticated is not equivalent to recovering access without the existing password. Likewise, revoking a credential is not the same as invalidating every authentication session previously established using that credential.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats these distinctions explicitly rather than assuming that every credential mutation has the same security effect.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-ownership-and-operation-scope\u0022\u003ECredential Ownership and Operation Scope\u003C/h2\u003E\n\u003Cp\u003ECredential management operations must be evaluated in the context of the actor, the target identity, and the requested operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between self-service, administrative, and anonymous recovery scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-operations\u0022\u003ESelf-Service Operations\u003C/h3\u003E\n\u003Cp\u003EAn authenticated user may manage credentials associated with their own identity, subject to the applicable permissions and validation rules.\u003C/p\u003E\n\u003Cp\u003EChanging a secret may require proving knowledge of the existing secret or satisfying another supported security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-operations\u0022\u003EAdministrative Operations\u003C/h3\u003E\n\u003Cp\u003EAdministrative credential management is performed against an explicitly identified target.\u003C/p\u003E\n\u003Cp\u003EAdministrative authority does not eliminate the requirement to preserve target identity consistency.\u003C/p\u003E\n\u003Cp\u003EFor example, a credential recovery operation must not modify a different user\u0027s credential merely because a supplied identifier resolves to another account.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-recovery\u0022\u003EAnonymous Recovery\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery allows an identity to regain access without an existing authenticated session.\u003C/p\u003E\n\u003Cp\u003EBecause the actor is not authenticated, the recovery process must establish sufficient proof before allowing credential modification.\u003C/p\u003E\n\u003Cp\u003EThese operation scopes are security boundaries, not merely different API routes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is one of the most security-sensitive responsibilities of the Credentials domain.\u003C/p\u003E\n\u003Cp\u003EThe password reset process is designed as a controlled, multi-stage operation rather than an unrestricted secret update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginning-recovery\u0022\u003EBeginning Recovery\u003C/h3\u003E\n\u003Cp\u003EA recovery request identifies the intended account and initiates a proof-based process.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported recovery configuration, proof may take the form of a token or code delivered through an appropriate notification mechanism.\u003C/p\u003E\n\u003Cp\u003EThe proof must be bound to the intended recovery context and subject to server-controlled validity rules.\u003C/p\u003E\n\u003Cp\u003ERecovery material must not be exposed to an anonymous caller simply because that caller initiated the request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completing-recovery\u0022\u003ECompleting Recovery\u003C/h3\u003E\n\u003Cp\u003ECompleting recovery requires validating the supplied proof and its associated security conditions before applying a new secret.\u003C/p\u003E\n\u003Cp\u003EThe reference recovery model accounts for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProof validity and expiration.\u003C/li\u003E\n\u003Cli\u003ESingle-use recovery material.\u003C/li\u003E\n\u003Cli\u003EConsistency between the recovery target and intended identity.\u003C/li\u003E\n\u003Cli\u003ERejection of invalid or previously consumed proof.\u003C/li\u003E\n\u003Cli\u003EProtection against unauthorized credential replacement.\u003C/li\u003E\n\u003Cli\u003ESecurity-state updates following successful recovery.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA completed recovery must not leave the same proof available for another successful reset.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enumeration-resistance\u0022\u003EEnumeration Resistance\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery endpoints must avoid unnecessarily revealing whether an identifier belongs to an existing account.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates externally observable recovery responses from the internal decisions required to locate and manage the account.\u003C/p\u003E\n\u003Cp\u003EThis reduces account enumeration opportunities while preserving the ability to enforce recovery rules.\u003C/p\u003E\n\u003Cp\u003ERecovery behavior should also be combined with appropriate abuse protection and operational monitoring.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-authentication-sessions\u0022\u003ECredentials and Authentication Sessions\u003C/h2\u003E\n\u003Cp\u003EChanging or recovering a credential may require action against authentication sessions that were established before the change.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ecredential lifecycle and session lifecycle are separate responsibilities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports security policies that determine how credential changes and resets affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EThe available policy model includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPolicy\u003C/th\u003E\n\u003Cth\u003EIntended behavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current session while revoking other applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe appropriate policy depends on the operation and the application\u0027s security requirements.\u003C/p\u003E\n\u003Cp\u003EA password reset performed through anonymous recovery may warrant different treatment from an authenticated password change.\u003C/p\u003E\n\u003Cp\u003EEnforcement also depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, revocation can be reflected through server-side session validation. In JWT-based modes, previously issued access tokens may remain usable until expiration unless additional validation mechanisms are involved.\u003C/p\u003E\n\u003Cp\u003ECredential changes therefore initiate the applicable security response; they do not imply identical immediate token invalidation across every mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E for the underlying differences.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022integration-with-the-users-domain\u0022\u003EIntegration with the Users Domain\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain does not need to own user records to manage authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EUser creation, deletion, or other lifecycle changes may require corresponding credential operations.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports coordination through integration contracts rather than requiring the Users and Credentials implementations to depend directly on one another.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may coordinate initial credential enrollment with user creation or perform credential cleanup as part of account removal.\u003C/p\u003E\n\u003Cp\u003EThese behaviors belong to explicit integration workflows.\u003C/p\u003E\n\u003Cp\u003ECreating a user does not inherently require creating a password credential, and deleting a user should not rely on an undocumented side effect to maintain credential consistency.\u003C/p\u003E\n\u003Cp\u003EThis distinction keeps identity lifecycle and credential lifecycle independently understandable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-credential-capabilities\u0022\u003EExtending Credential Capabilities\u003C/h2\u003E\n\u003Cp\u003ECredential mechanisms differ substantially in how they establish trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s architecture is intended to support additional mechanisms by separating credential-specific verification and management from authentication flow orchestration.\u003C/p\u003E\n\u003Cp\u003ESuch extensions may require their own:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential representation and metadata.\u003C/li\u003E\n\u003Cli\u003EEnrollment and verification procedures.\u003C/li\u003E\n\u003Cli\u003ESecurity and expiration rules.\u003C/li\u003E\n\u003Cli\u003ERecovery or revocation behavior.\u003C/li\u003E\n\u003Cli\u003EPersistence and integration contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every credential mechanism needs to implement password-style secret changes or recovery.\u003C/p\u003E\n\u003Cp\u003EThe goal is to preserve consistent authentication boundaries while allowing each credential mechanism to enforce its own security requirements.\u003C/p\u003E\n\u003Cp\u003EFor most applications using password authentication, the reference implementation provides the recommended starting point.\u003C/p\u003E\n\u003Cp\u003EApplications with specialized requirements can customize supported services and integrations or provide alternative implementations through the relevant contracts.\u003C/p\u003E\n\u003Cp\u003EDetailed extension and replacement strategies belong in the dedicated customization documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain establishes an important distinction within UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA user identifies the account. A credential provides authentication evidence. The authentication engine decides how that evidence establishes authentication state.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate allows the framework to support identity management without tying authentication to one user entity, one secret format, or one credential mechanism.\u003C/p\u003E\n\u003Cp\u003EThe current password implementation provides the operational foundation. The broader architecture allows credential capabilities to evolve without turning the authentication engine into a monolithic credential management system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Understand identity ownership, identifiers, and account lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Explore how authenticated identities are evaluated against access policies.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 Review the architecture and responsibilities of Plugin Domains.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the authentication flow that consumes credential verification results, see \u003Ca href=\u0022/docs/fundamentals/flow-based-authentication\u0022\u003EFlow-Based Authentication\u003C/a\u003E.\u003C/p\u003E\n",
+ "Html": "\n\u003Cp\u003EAuthentication establishes whether an entity can be trusted to act as a particular identity. A credential provides the evidence used to establish that trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats credentials as a distinct domain rather than embedding authentication secrets directly into user records or authentication flows.\u003C/p\u003E\n\u003Cp\u003EThe \u003Cstrong\u003ECredentials Plugin Domain\u003C/strong\u003E manages the lifecycle, verification, and security of authentication credentials while allowing the authentication engine to remain independent of a particular credential type.\u003C/p\u003E\n\u003Cp\u003EThe current reference implementation supports password credentials. Its architecture establishes the separation of responsibilities needed to accommodate additional credential mechanisms without redefining the underlying authentication model.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022understanding-credentials\u0022\u003EUnderstanding Credentials\u003C/h2\u003E\n\u003Cp\u003EA credential represents authentication material or a mechanism through which an identity can demonstrate possession of an accepted authentication factor.\u003C/p\u003E\n\u003Cp\u003ECredentials may take different forms:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ECredential mechanism\u003C/th\u003E\n\u003Cth\u003EAuthentication evidence\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPassword\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EKnowledge of a secret verified against a protected representation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EOne-time code\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EPossession of a time-limited or single-use authentication value\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECryptographic credential\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EProof of control over a private key or authenticator\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EExternal identity provider\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EA trusted authentication assertion established through an external protocol\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese mechanisms do not necessarily share the same storage, verification, or lifecycle requirements.\u003C/p\u003E\n\u003Cp\u003EFor example, a password requires secure secret verification, while a cryptographic credential may require signature validation and public-key metadata. An external provider may require validating protocol assertions rather than storing a reusable secret.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s credential architecture is intended to accommodate these differences through explicit credential-specific behavior.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003ECurrent implementation:\u003C/strong\u003E Password credentials are supported. The other mechanisms illustrate the broader credential model and its intended extension boundaries; they are not presented as implemented credential types.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-user-identity\u0022\u003ECredentials and User Identity\u003C/h2\u003E\n\u003Cp\u003EA user identity and a credential are separate concepts.\u003C/p\u003E\n\u003Cp\u003EThe Users domain manages identity ownership, identifiers, and account lifecycle. The Credentials domain manages the authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThis separation means:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EA user can exist independently of a particular credential.\u003C/li\u003E\n\u003Cli\u003ECredential state can change without redefining the user\u0027s identity.\u003C/li\u003E\n\u003Cli\u003EAn identity can conceptually be associated with multiple authentication mechanisms.\u003C/li\u003E\n\u003Cli\u003ECredential verification does not replace user lifecycle or account eligibility checks.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA username or email address is generally an \u003Cstrong\u003Eidentifier\u003C/strong\u003E, not a credential.\u003C/p\u003E\n\u003Cp\u003EAn identifier helps resolve which identity is involved in an authentication attempt. A credential provides evidence that the actor is entitled to authenticate as that identity.\u003C/p\u003E\n\u003Cp\u003EFor a password-based login, these responsibilities work together:\u003C/p\u003E\n\u003Col\u003E\n\u003Cli\u003EThe submitted identifier is resolved to an identity.\u003C/li\u003E\n\u003Cli\u003EThe applicable password credential is located.\u003C/li\u003E\n\u003Cli\u003EThe submitted secret is verified.\u003C/li\u003E\n\u003Cli\u003EAuthentication policies and relevant security conditions are evaluated.\u003C/li\u003E\n\u003Cli\u003EThe authentication engine establishes authentication state if the flow succeeds.\u003C/li\u003E\n\u003C/ol\u003E\n\u003Cp\u003EThe credential domain participates in establishing identity proof, but it does not independently determine the entire authentication outcome.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022responsibilities-across-the-framework\u0022\u003EResponsibilities Across the Framework\u003C/h2\u003E\n\u003Cp\u003ECredential support spans several architectural layers, each with a different responsibility.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ELayer\u003C/th\u003E\n\u003Cth\u003EResponsibility\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECore\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EFoundational authentication contracts and security concepts\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EServer\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EAuthentication flow execution, orchestration, and authentication-state management\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Bridge\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EContracts connecting credential capabilities to the authentication server\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Contracts\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EShared credential operation models and results\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ECredentials Reference Implementation\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ECredential management, validation, recovery, and lifecycle behavior\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EPersistence\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EStorage of credential records and related security state\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe distinction is between \u003Cstrong\u003Eusing credentials to authenticate\u003C/strong\u003E and \u003Cstrong\u003Emanaging credentials as domain resources\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EThe authentication engine coordinates authentication flows. The Credentials domain owns the rules governing credential records, their usability, and their lifecycle.\u003C/p\u003E\n\u003Cp\u003EAn application may integrate a custom credential system through the relevant authentication contracts without necessarily adopting the reference credential management implementation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-credentials\u0022\u003EPassword Credentials\u003C/h2\u003E\n\u003Cp\u003EPassword is the first supported credential mechanism in UltimateAuth.\u003C/p\u003E\n\u003Cp\u003EA password credential represents a password-based authentication factor associated with an identity.\u003C/p\u003E\n\u003Cp\u003EThe reference implementation separates the submitted secret from its protected representation and the credential\u0027s operational state.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022secret-protection\u0022\u003ESecret Protection\u003C/h3\u003E\n\u003Cp\u003EPasswords must not be stored as plaintext or returned through credential management responses.\u003C/p\u003E\n\u003Cp\u003EThe password verification process operates against a protected password representation using the configured password hashing infrastructure.\u003C/p\u003E\n\u003Cp\u003ECredential management APIs should expose only the information required for their intended operations, not the underlying secret.\u003C/p\u003E\n\u003Cp\u003EThis separation also allows password verification and password management to evolve without changing the user identity model.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-security-state\u0022\u003ECredential Security State\u003C/h3\u003E\n\u003Cp\u003EA credential is not necessarily usable merely because a matching secret has been supplied.\u003C/p\u003E\n\u003Cp\u003ECredential validation must also account for relevant lifecycle and security conditions, such as:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EWhether the credential is active or revoked.\u003C/li\u003E\n\u003Cli\u003EWhether an applicable expiration condition has been reached.\u003C/li\u003E\n\u003Cli\u003EWhether the credential belongs to the intended identity.\u003C/li\u003E\n\u003Cli\u003EWhether the requested operation is permitted.\u003C/li\u003E\n\u003Cli\u003EWhether the credential is eligible for the authentication mechanism being executed.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ESecret verification and credential usability are related but distinct decisions.\u003C/p\u003E\n\u003Cp\u003EA correctly supplied password must not make a revoked or otherwise unusable credential valid.\u003C/p\u003E\n\u003Cp\u003EPassword strength, common-password detection, user similarity, and password reuse are governed by configurable security policies.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/password-policy\u0022\u003EPassword Policy \u0026amp; History\u003C/a\u003E for the built-in policy levels, defaults, and configuration options.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-validation\u0022\u003ECredential Validation\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth separates credential-specific verification from the broader authentication flow.\u003C/p\u003E\n\u003Cp\u003EFor password authentication, credential validation includes evaluating the supplied secret against the stored protected representation and enforcing the relevant credential conditions.\u003C/p\u003E\n\u003Cp\u003EThe authentication server then uses the result within its authentication flow.\u003C/p\u003E\n\u003Cp\u003EThis boundary matters because credential verification is not equivalent to session creation or token issuance.\u003C/p\u003E\n\u003Cp\u003EA successful credential check establishes evidence for authentication. The authentication engine remains responsible for determining whether the flow can complete and what authentication state should be created.\u003C/p\u003E\n\u003Cp\u003EThe same architectural distinction applies to other credential mechanisms.\u003C/p\u003E\n\u003Cp\u003EA future cryptographic credential validator, for example, would verify a different kind of proof, but it would still participate in a broader authentication decision rather than independently issuing authentication state.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-lifecycle\u0022\u003ECredential Lifecycle\u003C/h2\u003E\n\u003Cp\u003ECredentials are managed resources with their own lifecycle.\u003C/p\u003E\n\u003Cp\u003EThe reference domain provides operations around credential creation, secret changes, revocation, and recovery.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EOperation\u003C/th\u003E\n\u003Cth\u003EPurpose\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EAdd\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003ERegister a credential for an identity\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EChange secret\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace authentication material through an authorized operation\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003ERevoke\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EMake a credential unusable\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EBegin reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EInitiate a controlled credential recovery process\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Cstrong\u003EComplete reset\u003C/strong\u003E\u003C/td\u003E\n\u003Ctd\u003EReplace a secret after satisfying recovery requirements\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese operations have different security requirements.\u003C/p\u003E\n\u003Cp\u003EChanging a password while authenticated is not equivalent to recovering access without the existing password. Likewise, revoking a credential is not the same as invalidating every authentication session previously established using that credential.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth treats these distinctions explicitly rather than assuming that every credential mutation has the same security effect.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-ownership-and-operation-scope\u0022\u003ECredential Ownership and Operation Scope\u003C/h2\u003E\n\u003Cp\u003ECredential management operations must be evaluated in the context of the actor, the target identity, and the requested operation.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth distinguishes between self-service, administrative, and anonymous recovery scenarios.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022self-service-operations\u0022\u003ESelf-Service Operations\u003C/h3\u003E\n\u003Cp\u003EAn authenticated user may manage credentials associated with their own identity, subject to the applicable permissions and validation rules.\u003C/p\u003E\n\u003Cp\u003EChanging a secret may require proving knowledge of the existing secret or satisfying another supported security requirement.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022administrative-operations\u0022\u003EAdministrative Operations\u003C/h3\u003E\n\u003Cp\u003EAdministrative credential management is performed against an explicitly identified target.\u003C/p\u003E\n\u003Cp\u003EAdministrative authority does not eliminate the requirement to preserve target identity consistency.\u003C/p\u003E\n\u003Cp\u003EFor example, a credential recovery operation must not modify a different user\u0027s credential merely because a supplied identifier resolves to another account.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022anonymous-recovery\u0022\u003EAnonymous Recovery\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery allows an identity to regain access without an existing authenticated session.\u003C/p\u003E\n\u003Cp\u003EBecause the actor is not authenticated, the recovery process must establish sufficient proof before allowing credential modification.\u003C/p\u003E\n\u003Cp\u003EThese operation scopes are security boundaries, not merely different API routes.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credential-recovery\u0022\u003ECredential Recovery\u003C/h2\u003E\n\u003Cp\u003ECredential recovery is one of the most security-sensitive responsibilities of the Credentials domain.\u003C/p\u003E\n\u003Cp\u003EThe password reset process is designed as a controlled, multi-stage operation rather than an unrestricted secret update.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022beginning-recovery\u0022\u003EBeginning Recovery\u003C/h3\u003E\n\u003Cp\u003EA recovery request identifies the intended account and initiates a proof-based process.\u003C/p\u003E\n\u003Cp\u003EDepending on the supported recovery configuration, proof may take the form of a token or code delivered through an appropriate notification mechanism.\u003C/p\u003E\n\u003Cp\u003EThe proof must be bound to the intended recovery context and subject to server-controlled validity rules.\u003C/p\u003E\n\u003Cp\u003ERecovery material must not be exposed to an anonymous caller simply because that caller initiated the request.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022completing-recovery\u0022\u003ECompleting Recovery\u003C/h3\u003E\n\u003Cp\u003ECompleting recovery requires validating the supplied proof and its associated security conditions before applying a new secret.\u003C/p\u003E\n\u003Cp\u003EThe reference recovery model accounts for:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EProof validity and expiration.\u003C/li\u003E\n\u003Cli\u003ESingle-use recovery material.\u003C/li\u003E\n\u003Cli\u003EConsistency between the recovery target and intended identity.\u003C/li\u003E\n\u003Cli\u003ERejection of invalid or previously consumed proof.\u003C/li\u003E\n\u003Cli\u003EProtection against unauthorized credential replacement.\u003C/li\u003E\n\u003Cli\u003ESecurity-state updates following successful recovery.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA completed recovery must not leave the same proof available for another successful reset.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enumeration-resistance\u0022\u003EEnumeration Resistance\u003C/h3\u003E\n\u003Cp\u003EAnonymous recovery endpoints must avoid unnecessarily revealing whether an identifier belongs to an existing account.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth separates externally observable recovery responses from the internal decisions required to locate and manage the account.\u003C/p\u003E\n\u003Cp\u003EThis reduces account enumeration opportunities while preserving the ability to enforce recovery rules.\u003C/p\u003E\n\u003Cp\u003ERecovery behavior should also be combined with appropriate abuse protection and operational monitoring.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022credentials-and-authentication-sessions\u0022\u003ECredentials and Authentication Sessions\u003C/h2\u003E\n\u003Cp\u003EChanging or recovering a credential may require action against authentication sessions that were established before the change.\u003C/p\u003E\n\u003Cp\u003EHowever, \u003Cstrong\u003Ecredential lifecycle and session lifecycle are separate responsibilities\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports security policies that determine how credential changes and resets affect existing sessions.\u003C/p\u003E\n\u003Cp\u003EThe available policy model includes:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EPolicy\u003C/th\u003E\n\u003Cth\u003EIntended behavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve existing sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003EKeepCurrent\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPreserve the current session while revoking other applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003ERevokeAll\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ERevoke all applicable sessions\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe appropriate policy depends on the operation and the application\u0027s security requirements.\u003C/p\u003E\n\u003Cp\u003EA password reset performed through anonymous recovery may warrant different treatment from an authenticated password change.\u003C/p\u003E\n\u003Cp\u003EEnforcement also depends on the authentication mode.\u003C/p\u003E\n\u003Cp\u003EIn session-authoritative modes, revocation can be reflected through server-side session validation. In JWT-based modes, previously issued access tokens may remain usable until expiration unless additional validation mechanisms are involved.\u003C/p\u003E\n\u003Cp\u003ECredential changes therefore initiate the applicable security response; they do not imply identical immediate token invalidation across every mode.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/fundamentals/auth-modes\u0022\u003EAuthentication Modes\u003C/a\u003E for the underlying differences.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022integration-with-the-users-domain\u0022\u003EIntegration with the Users Domain\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain does not need to own user records to manage authentication material associated with an identity.\u003C/p\u003E\n\u003Cp\u003EUser creation, deletion, or other lifecycle changes may require corresponding credential operations.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports coordination through integration contracts rather than requiring the Users and Credentials implementations to depend directly on one another.\u003C/p\u003E\n\u003Cp\u003EFor example, an application may coordinate initial credential enrollment with user creation or perform credential cleanup as part of account removal.\u003C/p\u003E\n\u003Cp\u003EThese behaviors belong to explicit integration workflows.\u003C/p\u003E\n\u003Cp\u003ECreating a user does not inherently require creating a password credential, and deleting a user should not rely on an undocumented side effect to maintain credential consistency.\u003C/p\u003E\n\u003Cp\u003EThis distinction keeps identity lifecycle and credential lifecycle independently understandable.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022extending-credential-capabilities\u0022\u003EExtending Credential Capabilities\u003C/h2\u003E\n\u003Cp\u003ECredential mechanisms differ substantially in how they establish trust.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth\u0027s architecture is intended to support additional mechanisms by separating credential-specific verification and management from authentication flow orchestration.\u003C/p\u003E\n\u003Cp\u003ESuch extensions may require their own:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECredential representation and metadata.\u003C/li\u003E\n\u003Cli\u003EEnrollment and verification procedures.\u003C/li\u003E\n\u003Cli\u003ESecurity and expiration rules.\u003C/li\u003E\n\u003Cli\u003ERecovery or revocation behavior.\u003C/li\u003E\n\u003Cli\u003EPersistence and integration contracts.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003ENot every credential mechanism needs to implement password-style secret changes or recovery.\u003C/p\u003E\n\u003Cp\u003EThe goal is to preserve consistent authentication boundaries while allowing each credential mechanism to enforce its own security requirements.\u003C/p\u003E\n\u003Cp\u003EFor most applications using password authentication, the reference implementation provides the recommended starting point.\u003C/p\u003E\n\u003Cp\u003EApplications with specialized requirements can customize supported services and integrations or provide alternative implementations through the relevant contracts.\u003C/p\u003E\n\u003Cp\u003EDetailed extension and replacement strategies belong in the dedicated customization documentation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022architectural-perspective\u0022\u003EArchitectural Perspective\u003C/h2\u003E\n\u003Cp\u003EThe Credentials domain establishes an important distinction within UltimateAuth:\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EA user identifies the account. A credential provides authentication evidence. The authentication engine decides how that evidence establishes authentication state.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EKeeping these responsibilities separate allows the framework to support identity management without tying authentication to one user entity, one secret format, or one credential mechanism.\u003C/p\u003E\n\u003Cp\u003EThe current password implementation provides the operational foundation. The broader architecture allows credential capabilities to evolve without turning the authentication engine into a monolithic credential management system.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/users\u0022\u003EUsers\u003C/a\u003E \u2014 Understand identity ownership, identifiers, and account lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/authorization\u0022\u003EAuthorization\u003C/a\u003E \u2014 Explore how authenticated identities are evaluated against access policies.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains\u0022\u003EPlugin Domains\u003C/a\u003E \u2014 Review the architecture and responsibilities of Plugin Domains.\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EFor the authentication flow that consumes credential verification results, see \u003Ca href=\u0022/docs/fundamentals/flow-based-authentication\u0022\u003EFlow-Based Authentication\u003C/a\u003E.\u003C/p\u003E\n",
"Headings": [
{
"Id": "understanding-credentials",
@@ -109,6 +109,6 @@
"Level": 0
}
],
- "SourceHash": "8618A9ED0DF478D9B489CBC7C51420145259EB1DBA1956617ECF3600D163BF2F",
+ "SourceHash": "5EEC9DEC644EEAB9AFBF7958DBA283FA11077DADF5ABBFCF3FB7493087B17106",
"BuilderVersion": "1"
}
\ No newline at end of file
diff --git a/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/password-policy.json b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/password-policy.json
new file mode 100644
index 00000000..5391c5d7
--- /dev/null
+++ b/docs/website/CodeBeam.UltimateAuth.Docs.Wasm/CodeBeam.UltimateAuth.Docs.Wasm.Client/wwwroot/docs/security/password-policy.json
@@ -0,0 +1,94 @@
+{
+ "Slug": "security/password-policy",
+ "Title": "Password Policy \u0026 History",
+ "Html": "\n\u003Cp\u003EUltimateAuth provides configurable password security policies for applications using its reference password credential implementation.\u003C/p\u003E\n\u003Cp\u003EInstead of requiring every application to implement password validation independently, UltimateAuth provides three built-in policy levels: \u003Cstrong\u003EBasic\u003C/strong\u003E, \u003Cstrong\u003EStandard\u003C/strong\u003E, and \u003Cstrong\u003EStrict\u003C/strong\u003E.\u003C/p\u003E\n\u003Cp\u003EEach level defines a starting set of password requirements. Developers can override individual requirements without replacing the entire policy.\u003C/p\u003E\n\u003Cp\u003EPassword validation is enforced on the server. Client applications can retrieve the effective policy to display requirements and provide immediate feedback.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-policy-levels\u0022\u003EPassword Policy Levels\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth includes three built-in presets.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EStandard is the default policy level.\u003C/strong\u003E\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ERequirement\u003C/th\u003E\n\u003Cth\u003EBasic\u003C/th\u003E\n\u003Cth\u003EStandard (Default)\u003C/th\u003E\n\u003Cth\u003EStrict\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMinimum length\u003C/td\u003E\n\u003Ctd\u003E8\u003C/td\u003E\n\u003Ctd\u003E8\u003C/td\u003E\n\u003Ctd\u003E15\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMaximum length\u003C/td\u003E\n\u003Ctd\u003E128\u003C/td\u003E\n\u003Ctd\u003E128\u003C/td\u003E\n\u003Ctd\u003E128\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire letter\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire digit\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire uppercase\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire lowercase\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ERequire special character\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003ENo\u003C/td\u003E\n\u003Ctd\u003EYes\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003ECommon-password detection\u003C/td\u003E\n\u003Ctd\u003EBasic\u003C/td\u003E\n\u003Ctd\u003EStandard\u003C/td\u003E\n\u003Ctd\u003EStrict\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EUser-similarity detection\u003C/td\u003E\n\u003Ctd\u003EBasic\u003C/td\u003E\n\u003Ctd\u003EStandard\u003C/td\u003E\n\u003Ctd\u003EStrict\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EPrevious passwords remembered\u003C/td\u003E\n\u003Ctd\u003E0\u003C/td\u003E\n\u003Ctd\u003E0\u003C/td\u003E\n\u003Ctd\u003E0\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThese values are the built-in defaults, not fixed limits for every application.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022basic\u0022\u003EBasic\u003C/h3\u003E\n\u003Cp\u003EBasic provides a minimum password length without requiring particular character classes.\u003C/p\u003E\n\u003Cp\u003EIt still enables basic common-password and user-similarity checks.\u003C/p\u003E\n\u003Cp\u003EThis preset is intended for applications that prefer fewer composition requirements while retaining server-side checks against predictable passwords.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022standard\u0022\u003EStandard\u003C/h3\u003E\n\u003Cp\u003EStandard is the default UltimateAuth password policy.\u003C/p\u003E\n\u003Cp\u003EIt requires at least eight characters, including a letter and a digit, and enables Standard-level common-password and user-similarity checks.\u003C/p\u003E\n\u003Cp\u003EApplications do not need to explicitly select Standard unless they want to make their configuration intentional.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022strict\u0022\u003EStrict\u003C/h3\u003E\n\u003Cp\u003EStrict increases the minimum length to 15 characters and requires letters, digits, uppercase letters, lowercase letters, and special characters.\u003C/p\u003E\n\u003Cp\u003EIt also selects Strict-level common-password and user-similarity checks.\u003C/p\u003E\n\u003Cp\u003EStrict is available for applications that require more restrictive password composition rules. It is not automatically the best policy for every application: usability, password-manager compatibility, account recovery, and the application\u0027s threat model should also be considered.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022configuring-password-policies\u0022\u003EConfiguring Password Policies\u003C/h2\u003E\n\u003Cp\u003EPassword policies are configured through \u003Ccode\u003EUAuthServerOptions.Password\u003C/code\u003E.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022use-a-built-in-preset\u0022\u003EUse a built-in preset\u003C/h3\u003E\n\u003Cp\u003EIn \u003Ccode\u003EProgram.cs\u003C/code\u003E:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Credentials.Contracts;\n\nbuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Password.Level = PasswordPolicyLevel.Strict;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis applies the Strict preset, including its default length, character requirements, and optional security checks.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022customize-individual-requirements\u0022\u003ECustomize individual requirements\u003C/h3\u003E\n\u003Cp\u003EYou can override any supported requirement without creating a custom policy implementation.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Eusing CodeBeam.UltimateAuth.Credentials.Contracts;\n\nbuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Password.Level = PasswordPolicyLevel.Standard;\n\n options.Password.MinLength = 12;\n options.Password.MaxLength = 64;\n\n options.Password.RequireUppercase = true;\n options.Password.RequireSpecialCharacter = true;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThe effective policy starts with Standard and applies the explicitly configured overrides.\u003C/p\u003E\n\u003Cp\u003EUnspecified requirements retain their preset values.\u003C/p\u003E\n\u003Cp\u003EFor example, this configuration still requires a letter and a digit, even though those properties are not explicitly set.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022disable-an-individual-security-check\u0022\u003EDisable an individual security check\u003C/h3\u003E\n\u003Cp\u003ECommon-password detection and user-similarity detection are configured independently from the main preset.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Password.Level = PasswordPolicyLevel.Standard;\n\n options.Password.CommonPasswords =\n PasswordPolicyLevel.Strict;\n\n options.Password.UserSimilarity =\n PasswordPolicyLevel.Disabled;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003EThis keeps Standard password composition rules, increases common-password detection to Strict, and disables user-similarity detection.\u003C/p\u003E\n\u003Cp\u003EDisabling security checks should be a deliberate application decision.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-length-limits\u0022\u003EPassword Length Limits\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth defines the following limits:\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003ESetting\u003C/th\u003E\n\u003Cth\u003EValue\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EMinimum configurable password length\u003C/td\u003E\n\u003Ctd\u003E6\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EDefault maximum password length\u003C/td\u003E\n\u003Ctd\u003E128\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003EAbsolute input length limit\u003C/td\u003E\n\u003Ctd\u003E256\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe built-in presets use a maximum length of 128 characters.\u003C/p\u003E\n\u003Cp\u003EApplications can customize the configured maximum within the supported limits. The absolute limit is a defensive boundary and is not intended to replace application-level password policy configuration.\u003C/p\u003E\n\u003Cp\u003EInvalid password policy options are rejected during server options validation.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022common-password-detection\u0022\u003ECommon-Password Detection\u003C/h2\u003E\n\u003Cp\u003EA password may satisfy every character requirement and still be easy to guess.\u003C/p\u003E\n\u003Cp\u003EFor example, a password such as \u003Ccode\u003Eqwerty123\u003C/code\u003E may satisfy Standard\u0027s letter, digit, and length requirements but still be rejected by the server\u0027s common-password checker.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth supports independently configurable detection levels:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EDisabled\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EBasic\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EStandard\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EStrict\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe selected level determines the checker configuration used by the reference implementation.\u003C/p\u003E\n\u003Cp\u003ECommon-password detection is a server-side operation. A client-side character validator cannot establish whether a password passes this check.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022user-similarity-detection\u0022\u003EUser-Similarity Detection\u003C/h2\u003E\n\u003Cp\u003EPasswords that closely resemble account identifiers or other available user information may be predictable.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides user-similarity checking with the same configurable levels:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ccode\u003EDisabled\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EBasic\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EStandard\u003C/code\u003E\u003C/li\u003E\n\u003Cli\u003E\u003Ccode\u003EStrict\u003C/code\u003E\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EThe server evaluates similarity using the available password-policy context.\u003C/p\u003E\n\u003Cp\u003EClient applications should not attempt to reproduce the complete similarity decision from public policy information alone.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022password-history\u0022\u003EPassword History\u003C/h2\u003E\n\u003Cp\u003EPassword history helps prevent users from repeatedly selecting passwords they have used before.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth can remember a configurable number of previous passwords and reject reuse during applicable password credential operations.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022enable-password-history\u0022\u003EEnable password history\u003C/h3\u003E\n\u003Cdiv class=\u0022docs-code-wrapper\u0022\u003E\u003Cdiv class=\u0022docs-code-header\u0022\u003E\u003Cspan class=\u0022docs-code-language\u0022\u003EC#\u003C/span\u003E\u003Cbutton type=\u0022button\u0022 class=\u0022docs-code-copy\u0022 aria-label=\u0022Copy code\u0022\u003ECopy\u003C/button\u003E\u003C/div\u003E\u003Cpre\u003E\u003Ccode class=\u0022language-csharp\u0022\u003Ebuilder.Services.AddUltimateAuthServer(options =\u0026gt;\n{\n options.Password.History.RememberLast = 5;\n});\n\u003C/code\u003E\u003C/pre\u003E\u003C/div\u003E\n\u003Cp\u003E\u003Ccode\u003ERememberLast\u003C/code\u003E controls how many previous passwords are checked.\u003C/p\u003E\n\u003Cdiv class=\u0022docs-table-wrapper\u0022\u003E\u003Ctable\u003E\n\u003Cthead\u003E\n\u003Ctr\u003E\n\u003Cth\u003EValue\u003C/th\u003E\n\u003Cth\u003EBehavior\u003C/th\u003E\n\u003C/tr\u003E\n\u003C/thead\u003E\n\u003Ctbody\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003E0\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003EPrevious-password history checking is disabled\u003C/td\u003E\n\u003C/tr\u003E\n\u003Ctr\u003E\n\u003Ctd\u003E\u003Ccode\u003E1\u201310\u003C/code\u003E\u003C/td\u003E\n\u003Ctd\u003ECheck up to the configured number of previous passwords\u003C/td\u003E\n\u003C/tr\u003E\n\u003C/tbody\u003E\n\u003C/table\u003E\u003C/div\u003E\n\u003Cp\u003EThe default value is \u003Ccode\u003E0\u003C/code\u003E.\u003C/p\u003E\n\u003Cp\u003EThe reference password-history validator also checks whether the submitted password matches the current credential, independently of the configured previous-password count.\u003C/p\u003E\n\u003Cp\u003EPassword history compares password candidates against protected password representations; it does not require retaining previous passwords in plaintext.\u003C/p\u003E\n\u003Ch3 class=\u0022mud-scrollspy-section\u0022 id=\u0022persistence\u0022\u003EPersistence\u003C/h3\u003E\n\u003Cp\u003EPassword history requires an appropriate history store.\u003C/p\u003E\n\u003Cp\u003EUltimateAuth provides reference storage implementations for in-memory and Entity Framework Core persistence.\u003C/p\u003E\n\u003Cp\u003EFor persistent deployments, apply the required database migrations before enabling password-history functionality.\u003C/p\u003E\n\u003Cp\u003EAn in-memory store is useful for development and testing but does not provide durable history across application restarts.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022server-enforcement-and-client-feedback\u0022\u003EServer Enforcement and Client Feedback\u003C/h2\u003E\n\u003Cp\u003EUltimateAuth distinguishes between authoritative password validation and client-side guidance.\u003C/p\u003E\n\u003Cp\u003E\u003Cstrong\u003EThe server is the security boundary.\u003C/strong\u003E\u003C/p\u003E\n\u003Cp\u003EClient applications may retrieve the effective policy and validate its publicly expressible requirements, including:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003EMinimum and maximum length\u003C/li\u003E\n\u003Cli\u003ELetter and digit requirements\u003C/li\u003E\n\u003Cli\u003EUppercase and lowercase requirements\u003C/li\u003E\n\u003Cli\u003ESpecial-character requirements\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EOther checks remain server-authoritative:\u003C/p\u003E\n\u003Cul\u003E\n\u003Cli\u003ECommon-password detection\u003C/li\u003E\n\u003Cli\u003EUser-similarity detection\u003C/li\u003E\n\u003Cli\u003EPassword history\u003C/li\u003E\n\u003C/ul\u003E\n\u003Cp\u003EA password passing client-side validation is not guaranteed to be accepted by the server.\u003C/p\u003E\n\u003Cp\u003EApplications should handle server-side password-policy violations as expected validation errors, rather than displaying them as unexpected application failures.\u003C/p\u003E\n\u003Cp\u003EFor client API usage and examples, see \u003Ca href=\u0022/docs/client/credentials\u0022\u003EClient SDK \u2014 Credentials\u003C/a\u003E.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022security-considerations\u0022\u003ESecurity Considerations\u003C/h2\u003E\n\u003Cp\u003EPassword policies are one part of credential security, not a replacement for the broader authentication system.\u003C/p\u003E\n\u003Cp\u003EApplications should also consider secure password hashing, transport security, login throttling, account recovery, session revocation, and appropriate authorization for credential-management operations.\u003C/p\u003E\n\u003Cp\u003EChanging a password may also affect existing authentication sessions, depending on the configured credential session policy.\u003C/p\u003E\n\u003Cp\u003ESee \u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E for the relevant session security boundaries.\u003C/p\u003E\n\u003Ch2 class=\u0022mud-scrollspy-section\u0022 id=\u0022next-steps\u0022\u003ENext Steps\u003C/h2\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/plugin-domains/credentials\u0022\u003ECredentials Domain\u003C/a\u003E \u2014 Understand credential ownership, validation, and lifecycle.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/client/credentials\u0022\u003EClient SDK \u2014 Credentials\u003C/a\u003E \u2014 Retrieve password requirements and implement credential-management flows.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/getting-started/real-world-setup\u0022\u003EReal-World Setup\u003C/a\u003E \u2014 Configure UltimateAuth in a real application.\u003C/li\u003E\n\u003Cli\u003E\u003Ca href=\u0022/docs/security/session-security\u0022\u003ESession Security Model\u003C/a\u003E \u2014 Understand the security consequences of credential changes.\u003C/li\u003E\n\u003C/ul\u003E\n",
+ "Headings": [
+ {
+ "Id": "password-policy-levels",
+ "Text": "Password Policy Levels",
+ "Level": 0
+ },
+ {
+ "Id": "basic",
+ "Text": "Basic",
+ "Level": 1
+ },
+ {
+ "Id": "standard",
+ "Text": "Standard",
+ "Level": 1
+ },
+ {
+ "Id": "strict",
+ "Text": "Strict",
+ "Level": 1
+ },
+ {
+ "Id": "configuring-password-policies",
+ "Text": "Configuring Password Policies",
+ "Level": 0
+ },
+ {
+ "Id": "use-a-built-in-preset",
+ "Text": "Use a built-in preset",
+ "Level": 1
+ },
+ {
+ "Id": "customize-individual-requirements",
+ "Text": "Customize individual requirements",
+ "Level": 1
+ },
+ {
+ "Id": "disable-an-individual-security-check",
+ "Text": "Disable an individual security check",
+ "Level": 1
+ },
+ {
+ "Id": "password-length-limits",
+ "Text": "Password Length Limits",
+ "Level": 0
+ },
+ {
+ "Id": "common-password-detection",
+ "Text": "Common-Password Detection",
+ "Level": 0
+ },
+ {
+ "Id": "user-similarity-detection",
+ "Text": "User-Similarity Detection",
+ "Level": 0
+ },
+ {
+ "Id": "password-history",
+ "Text": "Password History",
+ "Level": 0
+ },
+ {
+ "Id": "enable-password-history",
+ "Text": "Enable password history",
+ "Level": 1
+ },
+ {
+ "Id": "persistence",
+ "Text": "Persistence",
+ "Level": 1
+ },
+ {
+ "Id": "server-enforcement-and-client-feedback",
+ "Text": "Server Enforcement and Client Feedback",
+ "Level": 0
+ },
+ {
+ "Id": "security-considerations",
+ "Text": "Security Considerations",
+ "Level": 0
+ },
+ {
+ "Id": "next-steps",
+ "Text": "Next Steps",
+ "Level": 0
+ }
+ ],
+ "SourceHash": "2A46CBD9FFD1BA41AD57260E775CF74BA7F5DCD319F1415FDD2F68A6A8AF0BCF",
+ "BuilderVersion": "1"
+}
\ No newline at end of file
diff --git a/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor b/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor
index d88eb0a0..4ec15298 100644
--- a/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor
+++ b/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor
@@ -1,4 +1,5 @@
-@inject IUAuthClient UAuthClient
+@using CodeBeam.UltimateAuth.Credentials.Contracts
+@inject IUAuthClient UAuthClient
@inject ISnackbar Snackbar
@implements IDisposable
@@ -106,9 +107,29 @@
Margin="Margin.Dense"
Required="true"
RequiredError="Password is required."
+ Immediate="true"
+ MaxLength="@PasswordPolicyLimits.AbsoluteMaxLength"
+ Validation="@(new Func(ValidatePassword))"
autocomplete="new-password"
Disabled="@_isSubmitting" />
+ @if (_passwordPolicy is not null && PasswordRequirementsContent is not null)
+ {
+ @PasswordRequirementsContent(new PasswordRequirementsContext(_passwordPolicy, _password))
+ }
+ else if (_passwordPolicyLoading)
+ {
+
+ Loading password requirements...
+
+ }
+ else
+ {
+
+ Password requirements are verified by UltimateAuth.
+
+ }
+
-
-
-
-
- Password requirements are enforced by the configured UltimateAuth credential policy.
-
-
@@ -135,10 +149,7 @@
-
+
Cancel
diff --git a/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor.cs b/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor.cs
index 936d657a..7d65eafa 100644
--- a/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor.cs
+++ b/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CreateUserDialog.razor.cs
@@ -1,4 +1,6 @@
-using CodeBeam.UltimateAuth.Users.Contracts;
+using CodeBeam.UltimateAuth.Client;
+using CodeBeam.UltimateAuth.Credentials.Contracts;
+using CodeBeam.UltimateAuth.Users.Contracts;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using System.Net.Mail;
@@ -16,6 +18,13 @@ public partial class CreateUserDialog : IDisposable
private string? _displayName;
private bool _isSubmitting;
+ [Parameter]
+ public RenderFragment?
+ PasswordRequirementsContent
+ { get; set; }
+
+ private UAuthPasswordPolicyInfo? _passwordPolicy;
+ private bool _passwordPolicyLoading = true;
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = default!;
@@ -32,9 +41,26 @@ public partial class CreateUserDialog : IDisposable
/// The caller must enforce authorization and application-specific rules.
///
[Parameter]
- public Func>?
- OnCreateUserAsync
- { get; set; }
+ public Func>? OnCreateUserAsync { get; set; }
+
+ protected override async Task OnInitializedAsync()
+ {
+ try
+ {
+ var result = await UAuthClient.Credentials.GetPasswordPolicyAsync();
+
+ if (result.IsSuccess)
+ _passwordPolicy = result.Value;
+ }
+ catch (Exception)
+ {
+ // Server validation remains authoritative.
+ }
+ finally
+ {
+ _passwordPolicyLoading = false;
+ }
+ }
private async Task CreateUserAsync()
{
@@ -46,6 +72,14 @@ private async Task CreateUserAsync()
if (!_form.IsValid)
return;
+ var passwordError = ValidatePassword(_password);
+
+ if (passwordError is not null)
+ {
+ Snackbar.Add(passwordError, Severity.Error);
+ return;
+ }
+
if (!IdentifiersAvailable)
{
Snackbar.Add("Please wait until username and email availability checks are complete.", Severity.Warning);
@@ -110,6 +144,25 @@ private async Task CreateUserAsync()
}
}
+ private string? ValidatePassword(string? value)
+ {
+ if (string.IsNullOrEmpty(value))
+ return "Password is required.";
+
+ if (value.Length > PasswordPolicyLimits.AbsoluteMaxLength)
+ return "Password exceeds the maximum allowed length.";
+
+ if (_passwordPolicy is null)
+ return null;
+
+ var errors = UAuthPasswordClientValidator.Validate(
+ value, _passwordPolicy);
+
+ return errors.Count == 0
+ ? null
+ : "Password does not meet the configured requirements.";
+ }
+
private string? PasswordMatch(string? value)
{
return _password == value
diff --git a/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CredentialDialog.razor b/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CredentialDialog.razor
index 9f32bd10..def7795b 100644
--- a/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CredentialDialog.razor
+++ b/samples/CodeBeam.UltimateAuth.Sample.Shared/Components/Dialogs/CredentialDialog.razor
@@ -1,4 +1,5 @@
-@inject IUAuthClient UAuthClient
+@using CodeBeam.UltimateAuth.Credentials.Contracts
+@inject IUAuthClient UAuthClient
@inject ISnackbar Snackbar
@@ -83,8 +84,27 @@
Immediate="true"
Required="true"
RequiredError="New password is required."
+ MaxLength="@PasswordPolicyLimits.AbsoluteMaxLength"
+ Validation="@(new Func(ValidateNewPassword))"
Disabled="@_isSubmitting" />
+ @if (_passwordPolicy is not null && PasswordRequirementsContent is not null)
+ {
+ @PasswordRequirementsContent(new PasswordRequirementsContext(_passwordPolicy, _newPassword))
+ }
+ else if (_passwordPolicyLoading)
+ {
+
+ Loading password requirements...
+
+ }
+ else
+ {
+
+ Password requirements are validated by the server.
+
+ }
+
? PasswordRequirementsContent { get; set; }
+
[Parameter]
public bool PreviewMode { get; set; }
+ protected override async Task OnInitializedAsync()
+ {
+ try
+ {
+ var result = await UAuthClient.Credentials
+ .GetPasswordPolicyAsync();
+
+ if (result.IsSuccess)
+ _passwordPolicy = result.Value;
+ }
+ catch (Exception)
+ {
+ // The server remains authoritative.
+ }
+ finally
+ {
+ _passwordPolicyLoading = false;
+ }
+ }
+
+ private string? ValidateNewPassword(string? value)
+ {
+ if (string.IsNullOrEmpty(value))
+ return "New password is required.";
+
+ if (_passwordPolicy is null)
+ return null;
+
+ var errors = UAuthPasswordClientValidator.Validate(
+ value, _passwordPolicy);
+
+ return errors.Count == 0
+ ? null
+ : "Password does not meet the configured requirements.";
+ }
+
+
private async Task ChangePasswordAsync()
{
if (PreviewMode || _isSubmitting || _form is null)
diff --git a/samples/CodeBeam.UltimateAuth.Sample.Shared/Contracts/PasswordRequirementsContext.cs b/samples/CodeBeam.UltimateAuth.Sample.Shared/Contracts/PasswordRequirementsContext.cs
new file mode 100644
index 00000000..4d1b21a2
--- /dev/null
+++ b/samples/CodeBeam.UltimateAuth.Sample.Shared/Contracts/PasswordRequirementsContext.cs
@@ -0,0 +1,5 @@
+using CodeBeam.UltimateAuth.Credentials.Contracts;
+
+namespace CodeBeam.UltimateAuth.Sample.Shared.Components.Dialogs;
+
+public sealed record PasswordRequirementsContext(UAuthPasswordPolicyInfo Policy, string? Password);
diff --git a/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Migrations/20261102060746_003_PasswordHistory.Designer.cs b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Migrations/20261102060746_003_PasswordHistory.Designer.cs
new file mode 100644
index 00000000..0bfc6cee
--- /dev/null
+++ b/samples/UAuthHub/CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore/Migrations/20261102060746_003_PasswordHistory.Designer.cs
@@ -0,0 +1,765 @@
+//
+using System;
+using CodeBeam.UltimateAuth.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace CodeBeam.UltimateAuth.Sample.UAuthHub.EFCore.Migrations
+{
+ [DbContext(typeof(UAuthDbContext))]
+ [Migration("20261102060746_003_PasswordHistory")]
+ partial class _003_PasswordHistory
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authentication.EntityFrameworkCore.AuthenticationSecurityStateProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CredentialType")
+ .HasColumnType("INTEGER");
+
+ b.Property("FailedAttempts")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastFailedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("LockedUntil")
+ .HasColumnType("TEXT");
+
+ b.Property("RequiresReauthentication")
+ .HasColumnType("INTEGER");
+
+ b.Property("ResetAttempts")
+ .HasColumnType("INTEGER");
+
+ b.Property("ResetConsumedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ResetExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ResetRequestedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ResetTokenHash")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Scope")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "LockedUntil");
+
+ b.HasIndex("Tenant", "ResetRequestedAt");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "Scope");
+
+ b.HasIndex("Tenant", "UserKey", "Scope", "CredentialType")
+ .IsUnique();
+
+ b.ToTable("UAuth_Authentication", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RolePermissionProjection", b =>
+ {
+ b.Property("Tenant")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.Property("Permission")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Tenant", "RoleId", "Permission");
+
+ b.HasIndex("Tenant", "Permission");
+
+ b.HasIndex("Tenant", "RoleId");
+
+ b.ToTable("UAuth_RolePermissions", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.RoleProjection", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("DeletedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "Id")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "NormalizedName")
+ .IsUnique();
+
+ b.ToTable("UAuth_Roles", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Authorization.EntityFrameworkCore.UserRoleProjection", b =>
+ {
+ b.Property("Tenant")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("UserKey")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.Property("AssignedAt")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Tenant", "UserKey", "RoleId");
+
+ b.HasIndex("Tenant", "RoleId");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.ToTable("UAuth_UserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordCredentialProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("DeletedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("LastUsedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RevokedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("SecretHash")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("Source")
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "ExpiresAt");
+
+ b.HasIndex("Tenant", "Id")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "RevokedAt");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "DeletedAt");
+
+ b.ToTable("UAuth_PasswordCredentials", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Credentials.EntityFrameworkCore.PasswordHistoryProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("CredentialId")
+ .HasColumnType("TEXT");
+
+ b.Property("ReplacedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("SecretHash")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "CredentialId", "ReplacedAt", "Id");
+
+ b.ToTable("UAuth_PasswordHistory", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionChainProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AbsoluteExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("ActiveSessionId")
+ .HasColumnType("TEXT");
+
+ b.Property("ChainId")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimsSnapshot")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Device")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("DeviceId")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("LastSeenAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RevokedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("RootId")
+ .HasColumnType("TEXT");
+
+ b.Property("RotationCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityVersionAtCreation")
+ .HasColumnType("INTEGER");
+
+ b.Property("Tenant")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("TouchCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserKey")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("Version")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tenant", "ChainId")
+ .IsUnique();
+
+ b.HasIndex("Tenant", "RootId");
+
+ b.HasIndex("Tenant", "UserKey");
+
+ b.HasIndex("Tenant", "UserKey", "DeviceId");
+
+ b.ToTable("UAuth_SessionChains", (string)null);
+ });
+
+ modelBuilder.Entity("CodeBeam.UltimateAuth.Sessions.EntityFrameworkCore.SessionProjection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChainId")
+ .HasColumnType("TEXT");
+
+ b.Property("Claims")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Device")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Metadata")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("RevokedAt")
+ .HasColumnType("TEXT");
+
+ b.Property