Appearance
Security Features
GrydAuth includes enterprise-grade security features to protect your application against common threats.
Zero Trust Context Policies
Zero Trust now uses explicit validation contexts to avoid false positives and security drift:
PreAuthentication: used in unauthenticated flows such asPOST /api/v1/auth/loginand password reset request.Authenticated: used in authenticated flows such as refresh token, switch tenant, and protected APIs.
Policy Scope
PreAuthenticationruns only checks that are safe without a resolved user identity.Authenticatedruns full behavioral checks that depend on a known user/session.- Session integrity is explicitly skipped for bootstrap actions (
login,login_postauth,switch_tenant) and required for token/session continuation actions (for examplerefresh_tokenand authenticated API requests).
This separation prevents login from using user-bound checks before user identity exists.
Policy Resolver (SOLID)
Zero Trust check orchestration is now centralized in a dedicated policy resolver:
IZeroTrustPolicyResolverDefaultZeroTrustPolicyResolver
This removes rule branching from the validator internals and keeps policy decisions in one place (Context + Action), improving maintainability and reducing behavior drift across handlers/middleware.
Decision Model
- Blocking is now driven by explicit blocking signals only (
hard-block). - Risk score and soft signals produce
step-uprecommendation (RequiresAdditionalAuth) instead of automatic deny. RiskThresholdis used for step-up escalation, not for blind request denial.- Login hard-block now returns code
AUTH_RISK_FORBIDDEN(HTTP403).
Threat Intelligence Promotion Rules
- High-risk telemetry events are no longer enough by themselves to blacklist an IP.
- Threat intel promotion now requires explicit event allow-list (for example:
manual_ip_block,external_threat_feed_hit,credential_stuffing_detected) or explicit metadata override. - This prevents self-poisoning scenarios where internal validation telemetry blocks legitimate users.
Cache Isolation and TTL
- Zero Trust cache state is now context-scoped (
preauthvsauth) for threat intel and rate-limit dimensions. - Login-related threat intel keys use shorter retention (
ThreatIntelPreAuth = 15 minutes). - Authenticated threat intel keeps standard retention (
ThreatIntel = 1 hour). - This reduces cross-flow contamination and limits accidental lockout duration in pre-auth flows.
Canonical Security Session Store
Session integrity checks now use a dedicated store abstraction (ISecuritySessionStore) instead of ad-hoc cache key construction.
- Canonical key:
grydauth:security:session:{userId}:{sessionId} - Read/write/remove operations are centralized in
SecuritySessionStore - Zero Trust session validation no longer depends on implicit string keys
- Successful login persists a security session entry that is later consumed by authenticated zero-trust checks.
- Middleware resolves session ID in this order:
session_idclaim,X-Security-Session-Id/X-Session-Idheaders, then ASP.NET session ID fallback.
Operational Recovery Endpoint
To clear stale Zero Trust state without flushing Redis globally:
POST /api/v1/security/clear-state- Permission:
admin:system - Request body:
json
{
"ipAddress": "127.0.0.1",
"userId": "11111111-1111-1111-1111-111111111111"
}At least one of ipAddress or userId must be provided.
Observability Signals
Zero Trust now emits explicit decision telemetry:
zero_trust.context:PreAuthenticationorAuthenticatedzero_trust.decision:allow,step_up,blockedzero_trust.violations_countzero_trust.warnings_countzero_trust.requires_step_up
Structured logs also capture threat-intel source and promotion decisions for forensic analysis.
Error Contract Mapping
Zero Trust handler failures now use centralized mapping (ZeroTrustFailureMapper):
- Session-related blocking violations map to:
- message key:
Authentication.Session.InvalidOrExpired - code:
SESSION_INVALIDATED(HTTP401via canonical error-code mapping)
- message key:
- Other zero-trust blocking violations keep their original operation-specific contract (for example
AUTH_RISK_FORBIDDENin login).
Frontend Consumption Rules
POST /api/v1/auth/login:403+AUTH_RISK_FORBIDDEN=> bloqueio por risco (sem logout automatico).401+SESSION_INVALIDATED=> sessao invalida (iniciar reautenticacao).
- Endpoints autenticados:
401(SESSION_INVALIDATED,TOKEN_EXPIRED,TOKEN_INVALID,TOKEN_REVOKED) => limpar sessao local.403(MISSING_PERMISSION,FORBIDDEN,TENANT_FORBIDDEN,CROSS_TENANT_FORBIDDEN) => manter sessao e exibir sem acesso.
Regression Coverage
Security regression tests now cover:
- pre-auth vs post-auth zero-trust contexts in login flow
- regression:
login_postauthmust not be blocked by missing pre-existing session - hard-block semantics vs soft-signal step-up semantics
- anti self-poisoning rule for
zero_trust_validationevents - API contract for login risk blocking (
403+AUTH_RISK_FORBIDDEN) - middleware session ID propagation precedence (claim > header > ASP.NET session)
Rate Limiting
Protect endpoints against brute-force attacks and abuse.
Configuration
AddGrydAuth(configuration) registers native ASP.NET Core rate limiting and UseGrydAuth() adds it to the request pipeline. Configure the independent IP and normalized-account partitions in appsettings.json:
json
{
"GrydAuth": {
"Security": {
"RateLimiting": {
"Enabled": true,
"AuthIpPermitLimit": 60,
"AuthIpWindowSeconds": 60,
"AuthAccountPermitLimit": 10,
"AuthAccountWindowSeconds": 60,
"MaxRequestsPerIpPerHour": 10,
"MaxRequestsPerEmailPerHour": 3,
"PasswordResetWindowSeconds": 3600,
"QueueLimit": 0
}
}
}
}The built-in policies are auth-ip, auth-account, password-reset, and mfa. Rejections return 429, Retry-After, and the standard Gryd ProblemDetails contract.
Application rate limiting is defense in depth. Production deployments should also enforce coarse rate limits, connection limits, and bot protections at a trusted WAF or API gateway. Configure forwarded headers only for known proxies so the application receives the real client IP without trusting arbitrary X-Forwarded-For values.
Audit Logging
Track all security-related events.
Enable Audit Logging
csharp
builder.Services.AddGrydAuth(options =>
{
options.AuditLogging.Enabled = true;
options.AuditLogging.LogSuccessfulLogins = true;
options.AuditLogging.LogFailedLogins = true;
options.AuditLogging.LogPermissionChanges = true;
options.AuditLogging.LogTokenOperations = true;
options.AuditLogging.RetentionDays = 90;
});Audit Events
| Event | Description |
|---|---|
UserLogin | Successful login |
UserLoginFailed | Failed login attempt |
UserLogout | User logout |
UserRegistered | New user registration |
PasswordChanged | Password change |
PasswordResetRequested | Password reset request |
RoleAssigned | Role assigned to user |
RoleRevoked | Role removed from user |
PermissionGranted | Permission granted |
PermissionRevoked | Permission revoked |
TokenRefreshed | Token refreshed |
TokenRevoked | Token manually revoked |
AccountLocked | Account locked due to failed attempts |
AccountUnlocked | Account unlocked |
Querying Audit Logs
http
GET /api/v1/audit-logs?userId=550e8400-e29b-41d4-a716-446655440000&eventType=UserLogin&from=2026-01-01&to=2026-01-31
Authorization: Bearer {admin-token}Response:
json
{
"items": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"eventType": "UserLogin",
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"timestamp": "2026-01-15T10:30:00Z",
"success": true,
"metadata": {
"method": "Password",
"mfaUsed": false
}
}
],
"totalCount": 42,
"page": 1,
"pageSize": 20
}Custom Audit Events
csharp
public class OrderService
{
private readonly IAuditLogger _auditLogger;
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
var order = await _orderRepository.CreateAsync(request);
await _auditLogger.LogAsync(new AuditEvent
{
EventType = "OrderCreated",
EntityType = "Order",
EntityId = order.Id.ToString(),
Metadata = new Dictionary<string, object>
{
["amount"] = order.TotalAmount,
["items"] = order.Items.Count
}
});
return order;
}
}Geo-Location Tracking
Track login locations and detect suspicious activity.
Configuration
csharp
builder.Services.AddGrydAuth(options =>
{
options.GeoLocation.Enabled = true;
options.GeoLocation.Provider = GeoLocationProvider.MaxMind;
options.GeoLocation.BlockSuspiciousLogins = true;
options.GeoLocation.TrustedCountries = ["US", "CA", "GB", "DE"];
});MaxMind GeoIP Setup
csharp
builder.Services.AddMaxMindGeoIp(options =>
{
options.AccountId = Configuration["MaxMind:AccountId"]!;
options.LicenseKey = Configuration["MaxMind:LicenseKey"]!;
options.DatabasePath = Path.Combine(AppContext.BaseDirectory, "GeoLite2-City.mmdb");
options.AutoUpdate = true;
options.UpdateIntervalDays = 7;
});Location-Based Alerts
csharp
public class LocationSecurityHandler : ILoginEventHandler
{
private readonly INotificationService _notifications;
private readonly IGeoLocationService _geoLocation;
public async Task HandleLoginAsync(LoginEvent loginEvent)
{
var currentLocation = await _geoLocation.GetLocationAsync(loginEvent.IpAddress);
var lastLocation = await GetLastLoginLocationAsync(loginEvent.UserId);
if (lastLocation != null && IsUnusualLocation(currentLocation, lastLocation))
{
await _notifications.SendAsync(
loginEvent.UserId,
"New Login Location",
$"We detected a login from {currentLocation.City}, {currentLocation.Country}. " +
"If this wasn't you, please change your password immediately."
);
}
}
private bool IsUnusualLocation(GeoLocation current, GeoLocation last)
{
// Check if locations are suspiciously far apart for the time elapsed
var distance = CalculateDistance(current, last);
var timeElapsed = current.Timestamp - last.Timestamp;
var maxPossibleSpeed = 1000; // km/h (roughly commercial airline speed)
return distance / timeElapsed.TotalHours > maxPossibleSpeed;
}
}Token Security
Asymmetric Signing & JWKS
Tokens are signed with RS256 (default) or ES256. The private key lives only in GrydAuth; verifiers use the public keys published at /.well-known/jwks.json. The validator pins ValidAlgorithms and resolves keys by kid, so HS256, alg:none and forged tokens are rejected with 401. This replaces the v4 shared-secret (HS256) model — see the v4 → v5 migration guide for keys, configuration and rotation.
Token Invalidation
csharp
// Invalidate specific token
await _tokenService.RevokeTokenAsync(refreshToken);
// Invalidate all user tokens
await _tokenService.RevokeAllUserTokensAsync(userId);
// Invalidate with reason
await _tokenService.RevokeTokenAsync(refreshToken, new RevocationReason
{
Type = RevocationType.SecurityConcern,
Description = "Suspicious activity detected"
});Token Versioning
GrydAuth uses token versioning to invalidate tokens instantly:
csharp
public class User : AggregateRoot<Guid>
{
public long TokenVersion { get; private set; } = 1;
public void IncrementTokenVersion()
{
TokenVersion++;
AddDomainEvent(new TokensInvalidatedEvent(Id, TokenVersion));
}
}All tokens issued before the current version are automatically rejected.
Revocation Identity (jti)
Revocation decisions use the token's full jti as the single identity across every code path — never a truncated token hash. Cached token data carries jti, iat and type, so a principal rebuilt from cache always re-runs the blacklist check. This closes the v4 gap where tokens served from cache could skip revocation (a logout/revocation bypass). No configuration is required.
Blacklist Caching
csharp
builder.Services.AddGrydAuth(options =>
{
options.TokenBlacklist.Enabled = true;
options.TokenBlacklist.CacheProvider = BlacklistCacheProvider.Redis;
options.TokenBlacklist.CacheDuration = TimeSpan.FromDays(7);
});
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = Configuration.GetConnectionString("Redis");
options.InstanceName = "GrydAuth:";
});Cache Failure Policy (Fail-Secure)
When the security cache is unavailable, token validation is fail-secure by default: the token is rejected rather than accepted. This prevents a cache outage from re-enabling revoked or logged-out sessions, and matches the behavior of the token-version middleware.
jsonc
"GrydAuth": {
"TokenValidation": {
"CacheFailureMode": "FailSecure" // default. "FailOpen" restores legacy v4 behavior (not recommended)
}
}Changed in v5
v4 was fail-open on the user-blacklist (Layer 3) check — revoked tokens could be accepted during a cache outage. v5 rejects them. Failures are logged distinguishing cache unavailability from genuine revocation, without leaking PII.
Security Headers
GrydAuth automatically adds security headers:
csharp
app.UseGrydAuth(); // Adds security middleware
// Headers added:
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// X-XSS-Protection: 1; mode=block
// Referrer-Policy: strict-origin-when-cross-origin
// Content-Security-Policy: default-src 'self'Custom Security Headers
csharp
builder.Services.AddGrydAuth(options =>
{
options.SecurityHeaders.EnableHSTS = true;
options.SecurityHeaders.HSTSMaxAge = TimeSpan.FromDays(365);
options.SecurityHeaders.IncludeSubDomains = true;
options.SecurityHeaders.CustomHeaders = new Dictionary<string, string>
{
["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
};
});Input Validation
Automatic Sanitization
csharp
builder.Services.AddGrydAuth(options =>
{
options.InputValidation.SanitizeHtml = true;
options.InputValidation.PreventSqlInjection = true;
options.InputValidation.MaxRequestBodySize = 1024 * 1024; // 1MB
});Request Validation
csharp
public class LoginRequestValidator : AbstractValidator<LoginRequest>
{
public LoginRequestValidator()
{
RuleFor(x => x.Email)
.NotEmpty()
.EmailAddress()
.MaximumLength(256);
RuleFor(x => x.Password)
.NotEmpty()
.MinimumLength(8)
.MaximumLength(128)
.Must(NotContainSqlInjection).WithMessage("Invalid characters detected");
}
private bool NotContainSqlInjection(string password)
{
var sqlPatterns = new[] { "'", "--", ";", "/*", "*/", "xp_" };
return !sqlPatterns.Any(p => password.Contains(p, StringComparison.OrdinalIgnoreCase));
}
}CORS Configuration
CORS is secure by default in v5. Use AddGrydAuthCors, which reads the allowed origins from configuration and denies cross-origin requests when no origins are set outside Development (logging a configuration error at startup). AllowCredentials is enabled only when explicit origins are present — there is no AllowAnyOrigin fallback.
jsonc
"GrydAuth": {
"Cors": {
"AllowedOrigins": [ "https://yourapp.com", "https://admin.yourapp.com" ]
}
}csharp
builder.Services.AddGrydAuthCors(builder.Configuration); // policy name: "GrydAuthCors"
app.UseCors("GrydAuthCors");TIP
The legacy Cors:AllowedUrls (comma-separated) is still read as a fallback. In Development, common localhost origins are allowed automatically when nothing is configured. See the v4 → v5 migration guide.
Encryption
Data Encryption
csharp
builder.Services.AddGrydAuth(options =>
{
options.Encryption.EncryptSensitiveData = true;
options.Encryption.Algorithm = EncryptionAlgorithm.AES256;
options.Encryption.KeyVaultUrl = Configuration["Azure:KeyVaultUrl"];
});Password Hashing
GrydAuth uses Argon2id through Geralt/libsodium and stores passwords as versioned PHC strings. The hashing parameters are validated during startup:
json
{
"GrydAuth": {
"PasswordHashing": {
"MemorySizeKiB": 19456,
"Iterations": 2,
"Parallelism": 1,
"MaxConcurrency": 4
}
}
}MemorySizeKiB, Iterations, and Parallelism cannot be configured below the security baseline (19456, 2, and 1). MaxConcurrency bounds simultaneous memory-hard operations and must be calibrated against the service memory limit and authentication SLA.
Increasing the memory or iteration parameters causes the next successful login to transparently replace the user's hash. A persistence failure does not reject an otherwise valid login.
Breaking migration
PBKDF2 hashes from versions before this change are intentionally unsupported. Before deployment, force a password reset for every existing local account and invalidate outstanding reset/session credentials according to the operational runbook. There is no legacy verification fallback.
Security Monitoring
Health Checks
csharp
builder.Services.AddHealthChecks()
.AddGrydAuthHealthChecks();
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});Security Metrics
csharp
builder.Services.AddGrydAuth(options =>
{
options.Metrics.Enabled = true;
options.Metrics.Provider = MetricsProvider.Prometheus;
});
// Metrics exposed:
// gryd_auth_login_attempts_total
// gryd_auth_login_failures_total
// gryd_auth_tokens_issued_total
// gryd_auth_tokens_revoked_total
// gryd_auth_active_sessions_currentSecurity Checklist
Before going to production, ensure:
- [ ] JWT secret is stored in secure vault (not appsettings.json)
- [ ] HTTPS is enforced everywhere
- [ ] Rate limiting is enabled for auth endpoints
- [ ] Audit logging is enabled
- [ ] Password policy meets requirements
- [ ] Argon2id concurrency is calibrated for the container memory limit
- [ ] Existing local users completed the mandatory password reset migration
- [ ] Token expiration times are appropriate
- [ ] CORS is properly configured
- [ ] Security headers are enabled
- [ ] Database connections are encrypted (SSL/TLS)
- [ ] Sensitive data is encrypted at rest
- [ ] Regular security audits are scheduled