Exhaustive reference for every public type the library exposes (namespace MindAttic.Authentication.*).
For the why behind each control see SECURITY_SPEC.md; for wiring see
INTEGRATION.md; for config keys see CONFIGURATION.md.
Versioning: major-version-only. The assembly/package version is
4.0.0; the next release is6.0.0— never a minor or patch bump. The frozen behaviors below only change across a major bump. SeeVERSIONING.md.
public static class MaClaims {
public const string UserId = "ma:uid";
public const string SecurityStamp = "ma:stamp";
public const string SessionId = "ma:sid";
public const string Amr = "amr"; // "pwd", "mfa"
}
public static class MaRoles { public const string Admin = "Admin"; }
public static class MaPolicies { public const string Admin = "ma:admin"; } // RequireRole(Admin) + RequireClaim(amr,"mfa")
public static class MaSchemes {
public const string Cookie = "MindAttic.Auth";
public const string MfaPending = "MindAttic.MfaPending"; // short-lived, pre-MFA, grants no app access
}| Type | Bound from | Key members (defaults) |
|---|---|---|
AuthCryptoOptions |
MindAttic:Auth:Crypto |
MemoryKiB=65536, Iterations=3, Parallelism=4, SaltBytes=16, HashBytes=32, MinPasswordChars=12, MaxPasswordChars=128, CurrentPepperKeyId="v1", MaxConcurrentHashes=0 (⇒ ProcessorCount). ValidateOrThrow() enforces OWASP floors at startup. Floor consts: Floor*. |
MfaOptions |
MindAttic:Auth:Mfa |
Issuer="MindAttic", Digits=6, PeriodSeconds=30, WindowSteps=1, SecretBytes=20, RecoveryCodeCount=10, RecoveryCodeBytes=10, PendingEnrollmentMinutes=10, RequireForAdmin=true. |
AuthPolicyOptions |
MindAttic:Auth:Policy |
MinLength=12, MaxLength=128, CheckHibp=true, HibpRangeBaseUrl, HibpTimeoutMs=2000, HibpFailOpen=true, HistoryDepth=5. Const HibpHttpClient. |
AuthSessionOptions |
MindAttic:Auth:Session |
AbsoluteTimeout=8h, IdleTimeout=30m, RevalidationInterval=1m. |
MindAtticAuthOptions |
(host code) | AppName="App", ConfigureAdditionalPolicies, ConfigureDataProtection, DevKeyRingPath, IsProduction. |
public interface IAuthSecrets {
string GetRequired(string name); // throws if blank — fail-closed
string? GetOptional(string name);
byte[] GetRequiredBytes(string name); // base64-decoded
}
public sealed class ConfigAuthSecrets : IAuthSecrets { // reads MindAttic:Vault:Security:<name>, caches
public const string SectionPath = "MindAttic:Vault:Security";
}Required secrets: pepper.v1 (≥32 random bytes, base64), bootstrap-token (≥12 chars). Reserved:
dp-kek, reset-token-key, captcha-secret.
public readonly record struct PasswordHash(string Phc, string PepperKeyId);
public readonly record struct PasswordVerifyResult(bool Succeeded, bool NeedsRehash);
public interface IPasswordHasher {
PasswordHash Hash(string password);
PasswordVerifyResult Verify(string password, string storedHash, string? pepperKeyId, string? legacyScheme);
void VerifyDecoy(string password); // uniform-cost no-op for absent users
}
public sealed class Argon2idPasswordHasher : IPasswordHasher, IDisposable { }
public readonly record struct PhcArgon2(int Version, int MemoryKiB, int Iterations, int Parallelism, byte[] Salt, byte[] Hash) {
public string Encode();
public static bool TryParse(string? s, out PhcArgon2 result);
}PHC format: $argon2id$v=19$m=65536,t=3,p=4$<b64salt>$<b64hash>. legacyScheme: "bcrypt" | "sha256" | null.
AuthUser, AuthUserMfa, AuthRecoveryCode, AuthSession, AuthLoginThrottle, AuthAuditLog,
AuthPasswordHistory, AuthPasswordResetToken (full column lists in SECURITY_SPEC.md
§"EF Schema"). Enums:
public enum ThrottleScope : byte { Account = 0, Ip = 1 }
public enum AuthEventType : byte { Login, Register, PasswordReset, MfaChallenge, ChangePassword,
MfaEnroll, RecoveryUsed, RecoveryRegen, MfaDisabled, MfaOperatorReset,
HibpOnlineSkipped, Logout }
public enum AuthOutcome : byte { Success, Failure, Throttled, StepUpRequired }
public enum AuthReasonCode: byte { Ok, UnknownUser, BadPassword, Locked, Unverified,
MfaRequired, MfaBad, Disabled, TokenInvalid, PolicyRejected } // server-onlypublic interface IAuthDataContext { // the app's DbContext implements this
DbSet<AuthUser> AuthUsers { get; }
DbSet<AuthUserMfa> AuthUserMfa { get; }
DbSet<AuthRecoveryCode> AuthRecoveryCodes { get; }
DbSet<AuthSession> AuthSessions { get; }
DbSet<AuthLoginThrottle> AuthLoginThrottles { get; }
DbSet<AuthAuditLog> AuthAuditLog { get; }
DbSet<AuthPasswordHistory> AuthPasswordHistory { get; }
DbSet<AuthPasswordResetToken> AuthPasswordResetTokens { get; }
Task<int> SaveChangesAsync(CancellationToken ct = default);
}
public static class AuthModel {
public const string DefaultSchema = "auth";
public const string ModelFingerprint = "auth-v2";
public static ModelBuilder ApplyMindAtticAuthConfiguration(this ModelBuilder b, string schema = DefaultSchema);
}public enum LoginStatus { Success, MfaRequired, Failed }
public sealed record LoginResult(LoginStatus Status, Guid? UserId = null, IReadOnlyList<Claim>? Claims = null);
public interface IAuthenticationService {
Task<LoginResult> LoginAsync(string userName, string password, string sourceIp, string userAgent, CancellationToken ct = default);
Task<LoginResult> ConfirmMfaAsync(Guid userId, string code, bool isRecoveryCode, string sourceIp, string userAgent, CancellationToken ct = default);
}
public readonly record struct ThrottleDecision(bool Allowed, TimeSpan RetryAfter);
public interface IAccountLockoutService {
Task<ThrottleDecision> CheckAsync(ThrottleScope scope, string rawKey, CancellationToken ct = default);
Task RecordFailureAsync(ThrottleScope scope, string rawKey, CancellationToken ct = default);
Task ResetAsync(ThrottleScope scope, string rawKey, CancellationToken ct = default);
}
public sealed class AccountLockoutService { public static TimeSpan BackoffFor(int consecutiveFailures); }
public sealed record AuthAuditEntry(AuthEventType EventType, AuthOutcome Outcome, AuthReasonCode Reason, Guid? UserId=null,
string? UserNameAttempted=null, string? AccountKeyRaw=null, string? SourceIpRaw=null, string? UserAgent=null, bool CaptchaPresented=false);
public interface IAuthAuditWriter { Task WriteAsync(AuthAuditEntry entry, CancellationToken ct = default); }
public readonly record struct PasswordPolicyResult(bool Ok, string? Reason);
public interface IPasswordPolicy { Task<PasswordPolicyResult> ValidateAsync(string password, Guid? userId = null, CancellationToken ct = default); }
public interface ITotpService {
byte[] GenerateSecret(); string ToBase32(byte[] secret);
string BuildOtpAuthUri(byte[] secret, string accountName);
long? Validate(byte[] secret, string code, long lastStepUsed);
IReadOnlyList<string> GenerateRecoveryCodes();
}
public interface IUserStore {
Task<AuthUser?> FindByUserNameAsync(string userName, CancellationToken ct = default);
Task<AuthUser?> FindByIdAsync(Guid id, CancellationToken ct = default);
Task<AuthUserMfa?> FindMfaAsync(Guid userId, CancellationToken ct = default);
Task<bool> AnyUsersAsync(CancellationToken ct = default);
Task SaveChangesAsync(CancellationToken ct = default);
void ApplyRehash(AuthUser user, string phc, string pepperKeyId);
void RecordLogin(AuthUser user, DateTime utcNow);
static string Normalize(string userName); // NFKC + trim + upper
}
public readonly record struct EnrollmentBeginResult(string SecretBase32, string OtpAuthUri);
public readonly record struct EnrollmentConfirmResult(bool Ok, IReadOnlyList<string>? RecoveryCodes, string? Error);
public interface IMfaEnrollmentService {
Task<EnrollmentBeginResult> BeginAsync(Guid userId, CancellationToken ct = default);
Task<EnrollmentConfirmResult> ConfirmAsync(Guid userId, string code, string currentPassword, CancellationToken ct = default);
}
public readonly record struct PasswordChangeResult(bool Ok, string? Error);
public interface IPasswordChangeService { Task<PasswordChangeResult> ChangeAsync(Guid userId, string currentPassword, string newPassword, CancellationToken ct = default); }
public sealed class AuthBootstrapper { Task SeedAdminAsync(string adminUserName = "admin", CancellationToken ct = default); }
// Auth email. AddMindAtticAuthentication picks SmtpAuthEmailSender when AuthEmailSettings load, else LoggingAuthEmailSender.
public interface IAuthEmailSender {
Task SendPasswordResetAsync(string toEmail, string resetLink, CancellationToken ct = default);
Task SendSecurityAlertAsync(string toEmail, string subject, string body, CancellationToken ct = default);
}
public sealed class LoggingAuthEmailSender : IAuthEmailSender; // log-only fallback
public sealed record AuthEmailSettings(string SmtpHost, int SmtpPort, string Username, string Password, string From) {
public const string SectionPath = "MindAttic:Vault:Notifications:email";
public static AuthEmailSettings? Load(IConfiguration configuration, out IReadOnlyList<string> problems);
}
public sealed record AuthEmailStatus(AuthEmailSettings? Settings, IReadOnlyList<string> Problems);
public interface IAuthMailTransport { Task SendAsync(MimeMessage message, CancellationToken ct); } // test seam
public sealed class MailKitAuthMailTransport(AuthEmailSettings settings) : IAuthMailTransport; // 465 = TLS, else STARTTLS
public sealed class SmtpAuthEmailSender : BackgroundService, IAuthEmailSender { // queued; 3 attempts; never throws to caller
public const int QueueCapacity = 256; public const int MaxAttempts = 3;
public TimeSpan RetryDelay { get; init; }
}
public sealed class AuthEmailStartup : IHostedService; // starts the SMTP queue; warns when mail will not be delivered
// Security alerts. Registered by AddMindAtticAuthEmail; the account services take it as an optional last
// constructor parameter (IAuthSecurityAlerts? alerts = null) and call it after the change is saved.
public enum SecurityAlertKind { PasswordChanged, PasswordReset, PasswordResetByAdmin, MfaEnabled, RecoveryCodeUsed,
AccountLocked, NewDeviceSignIn, EmailChanged, AccountDeactivated }
public interface IAuthSecurityAlerts {
Task NotifyAsync(AuthUser user, SecurityAlertKind kind, string? userAgent = null, string? toEmail = null, CancellationToken ct = default);
}
public sealed class AuthSecurityAlerts(IAuthEmailSender email, TimeProvider clock, ILogger<AuthSecurityAlerts> logger) : IAuthSecurityAlerts;public static IServiceCollection AddMindAtticAuthentication<TContext>(
this IServiceCollection services, IConfiguration config, Action<MindAtticAuthOptions> configure)
where TContext : DbContext, IAuthDataContext;
public static IServiceCollection AddMindAtticAuthEmail(this IServiceCollection services, IConfiguration config, string appName); // called by the above; TryAdd throughout
public static IApplicationBuilder UseMindAtticAuthentication(this IApplicationBuilder app); // authn→authz→scoped CSP
public static IEndpointRouteBuilder MapMindAtticAuthEndpoints(this IEndpointRouteBuilder endpoints);
// POST /_ma-auth/login · /mfa-challenge · /logout · /change-password (RequireAuthorization)
// /reset/request (→ /login?reset=sent) · /reset/confirm (→ /login?reset=ok, or back to ResetPath?error=1&token=…)
public sealed class MaRevalidatingAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider;
public static class MindAtticAuthAppExtensions { public const string CspNonceItem = "ma-csp-nonce"; }| Component | Render | Parameters | Posts to |
|---|---|---|---|
MaLogin |
static SSR form | ReturnUrl, Error |
/_ma-auth/login |
MaMfaChallenge |
static SSR form | ReturnUrl, Error |
/_ma-auth/mfa-challenge |
MaChangePassword |
static SSR form | Error |
/_ma-auth/change-password |
MaLogout |
static SSR form | Text |
/_ma-auth/logout |
MaMfaSetup |
interactive | — (reads auth state) | calls IMfaEnrollmentService directly; shows recovery codes once |
CSS hooks: .ma-auth, .ma-auth-field, .ma-auth-error, .ma-auth-submit, .ma-recovery-codes, etc.
No component ever calls SignInAsync.
Internal.AuthKeys (key canonicalization/hashing), Internal.UrlSafety (IsLocalUrl), Internal.TimingFloor.
Treat as implementation detail; not covered by the major-version stability promise.