Advanced Configuration
This guide covers complex scenarios such as custom user stores, high availability, and manual credential validation.
1. Class-Based User Store
For complex applications, you may prefer to implement IPrimusAuthUserStore as a class rather than using the inline Lambda configuration.
using PrimusSaaS.Identity.Broker;
using System.Security.Claims;
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;
return new PrimusAuthUser
{
Id = user.Id.ToString(),
Email = user.Email,
Role = user.Role
};
}
// Optional: Only needed for SSO (Azure/Google) provisioning
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;
}
Registration:
builder.Services.AddScoped<IPrimusAuthUserStore, AppUserStore>();
builder.Services.AddPrimusAuthBroker(...);
2. Custom Credential Validator
The default IPrimusAuthCredentialValidator is required only if you want to use the /api/auth/login endpoint (Username/Password). If you only use Azure AD/Okta, you can skip this.
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)
{
// Your custom hashing logic here
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;
}
}
3. High Availability (Cluster/Kubernetes)
If you run multiple instances of your API, the session cookie encrypted by Server A must be readable by Server B.
Solution: Share the Data Protection Keys.
// Store keys in a shared folder (Azure Files, AWS S3, or Redis)
builder.Services.AddPrimusBrokerDataProtection("PrimusApp", new DirectoryInfo(@"/mnt/shared/keys"));