Advanced Configuration
This page covers scenarios that go beyond the defaults: custom user stores, cluster deployments, and built-in admin impersonation.
1. Class-Based User Store
If you need constructor-injected dependencies (a database context, a logger, etc.), implement IPrimusAuthUserStore as a class. For a simpler lambda-based approach that does not need DI, use .WithAutoProvision() instead (see Section 2).
public class AppUserStore : IPrimusAuthUserStore
{
private readonly AppDbContext _db;
public AppUserStore(AppDbContext db) => _db = db;
public async Task<PrimusAuthUser?> FindByEmailAsync(string email, CancellationToken ct = default)
{
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email, ct);
if (user == null) return null;
// Role populates the ClaimTypes.Role claim — used by [Authorize(Roles = "Admin")]
return new PrimusAuthUser { Id = user.Id.ToString(), Email = user.Email, Role = user.Role };
}
// Called on first SSO login — create the user record and return it.
// Return null to block unknown SSO accounts. Safe to return null for local-only apps.
public async Task<PrimusAuthUser?> AutoProvisionUserAsync(
string email, string provider, ClaimsPrincipal principal, CancellationToken ct = default)
{
var newUser = new User { Email = email, Source = provider };
_db.Users.Add(newUser);
await _db.SaveChangesAsync(ct);
return new PrimusAuthUser { Id = newUser.Id.ToString(), Email = newUser.Email };
}
public Task<bool> IsLockedOutAsync(string email, CancellationToken ct) => Task.FromResult(false);
public Task RecordLoginFailureAsync(string email, CancellationToken ct) => Task.CompletedTask;
public Task ResetLoginFailureAsync(string email, CancellationToken ct) => Task.CompletedTask;
}
Register it before AddPrimusAuthBroker. The broker uses TryAdd internally, so a store registered first takes precedence:
builder.Services.AddScoped<IPrimusAuthUserStore, AppUserStore>();
builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());
isDevelopment: true— auto-registersInMemoryPrimusAuthUserStoreif no store is found. Login works immediately without a database — useful for prototyping.isDevelopment: false— registersNullPrimusAuthUserStore, which blocks all logins and logs aCriticalstartup warning until you register your own store.
2. JIT Auto-Provisioning (SSO only)
WithAutoProvision is the simplest way to handle SSO user creation without a full class. checkUser is called on every SSO login; autoProvision is called only when checkUser returns null (i.e., the user does not exist yet).
builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment())
.WithAutoProvision(
checkUser: async (email, sp) =>
{
var db = sp.GetRequiredService<AppDbContext>();
var user = await db.Users.FirstOrDefaultAsync(u => u.Email == email);
return user == null ? null : new PrimusAuthUser { Id = user.Id, Email = user.Email, Role = user.Role };
},
autoProvision: async (email, provider, principal, sp) =>
{
var db = sp.GetRequiredService<AppDbContext>();
var newUser = new User { Email = email, Source = provider, Role = "User" };
db.Users.Add(newUser);
await db.SaveChangesAsync();
return new PrimusAuthUser { Id = newUser.Id.ToString(), Email = email, Role = "User" };
});
If checkUser returns null and WithAutoProvision is not configured, the SSO login is rejected and the user sees the configured ErrorRedirect.
3. Custom Credential Validator
Only needed if you use the /api/auth/login endpoint (email + password). Not required for SSO-only setups.
public class AppCredentialValidator : IPrimusAuthCredentialValidator
{
private readonly AppDbContext _db;
public AppCredentialValidator(AppDbContext db) => _db = db;
public async Task<PrimusAuthUser?> ValidateCredentialsAsync(string email, string password, CancellationToken ct)
{
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email);
if (user != null && VerifyHash(password, user.PasswordHash))
return new PrimusAuthUser { Id = user.Id, Email = user.Email };
return null;
}
}
Register it before AddPrimusAuthBroker:
builder.Services.AddScoped<IPrimusAuthCredentialValidator, AppCredentialValidator>();
builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());
4. High Availability (Cluster / Kubernetes)
Two things need to be shared across all replicas: session cookie keys and the rate limiter state.
Share Data Protection Keys — so a cookie encrypted by pod A can be read by pod B:
// Point all replicas at a shared volume (Azure Files, EFS, etc.)
builder.Services.AddPrimusBrokerDataProtection("PrimusApp", new DirectoryInfo(@"/mnt/shared/keys"));
InMemoryRateLimiter stores counters in process memory. Each pod maintains its own counter independently, so the effective limit across the cluster is maxRequests × pod count. To enforce a consistent global limit, register a distributed IPrimusAuthRateLimiter (e.g. Redis-backed) before AddPrimusAuthBroker:
builder.Services.AddSingleton<IPrimusAuthRateLimiter, MyRedisRateLimiter>();
builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());
5. Admin Impersonation
Admins can log in as any user to reproduce bugs without knowing their password.
// Impersonate (Admin role required)
await fetch('/api/auth/impersonate', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Primus-CSRF': getCsrfToken(),
},
body: JSON.stringify({ targetEmail: 'user@example.com' }),
});
// End impersonation
await fetch('/api/auth/revert', {
method: 'POST',
credentials: 'include',
headers: { 'X-Primus-CSRF': getCsrfToken() },
});
| Endpoint | Role |
|---|---|
POST /api/auth/impersonate | Admin or SuperAdmin |
POST /api/auth/revert | Any (with an active impersonation session) |
Both actions are recorded by IPrimusAuthAuditSink as ImpersonateStart and ImpersonateEnd.
POST /api/auth/revert clears your session entirely — it does not restore your original admin session. You must log in again after reverting. This is intentional to avoid storing multiple concurrent session states.
Next Steps
- Integration Guide — complete setup walkthrough from install to first authenticated request
- Endpoint Reference — all impersonation, passkey, and admin endpoints with full request and response shapes
- Database Setup — persist users with EF Core and a full list of built-in implementations
- Bot Protection & Rate Limiting — Redis-backed distributed rate limiting for multi-instance deployments