How to Protect ASP.NET Core APIs Against Broken Authentication

Broken authentication is the second entry on the OWASP API Security Top 10 for good reason โ it is one of the most consistently exploited vulnerabilities in production APIs, and ASP.NET Core teams are not immune. Unlike BOLA, which is about authorising access to the right object, broken authentication is about whether the identity itself can be trusted in the first place. When authentication breaks down, every other security control downstream becomes unreliable.
The gap between teams that implement JWT correctly and teams that implement it insecurely is rarely about intent โ it is almost always about the specific defaults they accepted without questioning them. The complete authentication patterns, including token families, refresh token rotation, and secure key management, are available with working source code on Patreon โ ready to drop into a real production codebase.
If you want to see these authentication concepts wired together inside a full production API โ including token rotation, policy-based authorization, and hardened defaults โ Chapter 7 of the ASP.NET Core Web API: Zero to Production course covers exactly that, with a complete codebase you can run immediately.
What Broken Authentication Actually Means in ASP.NET Core APIs
OWASP API2:2023 (Broken Authentication) covers a broad class of weaknesses that all share one root cause: the API cannot reliably verify that the calling party is who they claim to be. In ASP.NET Core APIs, this typically surfaces in one of five patterns:
Weak or predictable signing keys โ JWT secrets that are too short, hardcoded in source control, or shared across environments
Disabled or incomplete token validation โ accepting tokens with
alg: none, skipping audience/issuer checks, or settingClockSkewtoo highInsecure token transmission โ tokens passed over HTTP, logged to disk, or stored in
localStorageon the clientMissing expiry enforcement โ long-lived access tokens with no rotation strategy, or refresh tokens that never expire
Credential stuffing exposure โ login and token endpoints with no rate limiting, no account lockout, and no anomaly detection
Each of these represents a distinct attack surface, and each requires a different mitigation. A team that has fixed the key management problem but left validation parameters at their defaults is still vulnerable โ just to a different exploit.
The Threat: How Broken Authentication Is Exploited
Understanding how attackers approach broken authentication makes the mitigations more intuitive.
Weak Signing Keys
A JWT secret that is fewer than 256 bits (32 bytes) for HMAC-SHA256 can be brute-forced offline once an attacker has intercepted a token. This is not theoretical โ tools exist specifically for cracking JWT secrets, and they are effective against anything shorter than a strong random key. If the key is also checked into source control, the window of exposure extends indefinitely to anyone with repository access, past or present.
The fix is not complex. Use a cryptographically random key of at least 256 bits, store it in environment variables or a secrets manager (Azure Key Vault, AWS Secrets Manager), and never commit it to source control under any circumstances. The Options pattern in ASP.NET Core โ IOptions<JwtSettings> โ makes it straightforward to load signing keys from the configuration hierarchy at startup without hardcoding anything.
Disabled or Incomplete Token Validation
The TokenValidationParameters class in ASP.NET Core gives developers granular control over what gets validated. The problem is that several of the most important checks are not enforced by default, and teams that copy boilerplate JWT setup code often accept those defaults without reading what they mean.
The alg: none attack is the classic example. A JWT with the algorithm field set to none carries no cryptographic signature โ the server should reject it outright. Modern versions of System.IdentityModel.Tokens.Jwt handle this correctly by default, but hand-rolled or legacy validation logic may not. Always validate against a known set of accepted algorithms: ValidAlgorithms = new[] { SecurityAlgorithms.HmacSha256 }.
Audience and issuer validation are equally important. Skipping ValidateAudience or ValidateIssuer means a token issued for one of your services can be replayed against any other service that trusts the same key โ a lateral movement opportunity that attackers actively look for in multi-service architectures. Both should be enabled, and the values should be specific: not *, not empty, not a development placeholder left in place.
ClockSkew defaults to five minutes in ASP.NET Core. That means a token whose expiry says 14:00:00 is still accepted until 14:05:00 on any server. For most production systems, setting ClockSkew = TimeSpan.Zero is the correct posture. If you need a small tolerance for clock drift, keep it under thirty seconds and document why.
Token Transmission and Storage
A correctly signed, well-validated JWT is worthless if it travels over HTTP in plain text or ends up in a server log. Enforce HTTPS at the infrastructure level and use UseHttpsRedirection() as a fallback. For APIs, also consider rejecting requests that arrive without TLS โ a middleware check on context.Request.IsHttps is a simple safeguard.
Logging middleware is a common culprit for token leakage. Structured logging libraries like Serilog, when configured with request body or header logging, will happily capture the Authorization header. Ensure that Authorization, Cookie, and any other credential-bearing headers are added to a destructuring exclusion list in your Serilog configuration.
On the client side, localStorage is accessible to any JavaScript running on the page โ including injected scripts from a successful XSS attack. For web applications that consume your API, HttpOnly cookies provide a safer token storage mechanism. For mobile and server-to-server integrations, the token should live in memory only โ never written to persistent storage.
Token Expiry and Rotation
Long-lived access tokens are a significant risk because any leak gives the attacker a long operational window. The standard guidance โ and what production security postures converge on โ is short access tokens (10โ15 minutes) combined with a refresh token rotation strategy. When a refresh token is used to obtain a new access token, the old refresh token should be invalidated immediately. If a refresh token is used twice, that is a strong signal of theft, and the entire token family for that user session should be revoked.
ASP.NET Core does not include refresh token rotation out of the box. It requires a stored token table, a revocation check on every refresh request, and a token family concept to detect replay. This is where many teams shortcut and pay the price later. If you are building this for the first time, the effort is substantial but well worth it โ and it is covered in full in Chapter 7 of the Zero to Production course.
Reference tokens โ where the token is an opaque identifier that maps to session data stored server-side โ are an alternative approach that makes revocation trivially simple. The trade-off is that every request requires a database or cache lookup. For internal services where that overhead is acceptable, reference tokens can be a more operationally controllable choice. The JWT vs Reference Tokens Enterprise Decision Guide covers this trade-off in detail.
Credential Stuffing and Brute-Force on Auth Endpoints
The /auth/login and /auth/refresh endpoints deserve the same rate limiting treatment as any other high-value endpoint. Without it, they become targets for credential stuffing โ automated attacks that try large volumes of username/password combinations sourced from data breaches.
ASP.NET Core's built-in rate limiting middleware makes per-endpoint policies straightforward to apply. A fixed window policy on login โ limiting to a small number of attempts per IP per minute โ raises the cost of credential stuffing significantly. Combine this with a Retry-After response header on 429s and an account lockout mechanism (even a temporary soft lock) to push the cost even higher. The ASP.NET Core Rate Limiting Enterprise Policy Guide covers the implementation options in detail.
Defence-in-Depth Checklist
Is Your JWT Configuration Actually Secure?
No single control eliminates broken authentication risk. The effective mitigation is layering defences so that exploiting one weakness does not immediately compromise the system:
โ Signing key: Minimum 256-bit random value, stored in secrets manager, never in source control
โ Algorithm validation: Explicitly set
ValidAlgorithmsโ rejectnoneand any algorithm you are not actively usingโ Audience and issuer: Both validated, both specific to the environment
โ ClockSkew: Set to
TimeSpan.Zeroor under 30 secondsโ Access token expiry: 10โ15 minutes maximum for user-facing APIs
โ Refresh token rotation: Old token invalidated on use; token family revoked on replay detection
โ HTTPS enforcement:
UseHttpsRedirection()and TLS at infrastructure level; never accept credentials over HTTPโ Header logging exclusions:
AuthorizationandCookieheaders excluded from structured log outputโ Rate limiting on auth endpoints: Fixed or sliding window with
Retry-Afteron 429 responsesโ Account lockout: Temporary lockout or exponential back-off after repeated failed attempts
What Teams Get Wrong in Production
The most common failure mode is not a single wrong decision โ it is a series of small shortcuts that compound. A team hardcodes the JWT secret during development and never rotates it to a secrets manager before go-live. They leave ValidateAudience = false because it was causing errors during local testing. They set a one-week access token expiry because the product team did not want users to see re-authentication prompts. They log full request headers for debugging and forget to clean that up before production.
Each of these shortcuts is understandable in isolation. Together, they create an API where a single intercepted token gives an attacker a week of access, the signing key is readable from the repo, and validation parameters are loose enough to accept forged tokens from other services. This is not a hypothetical scenario โ it is the authentication posture of a significant fraction of production ASP.NET Core APIs that have not been through a formal security review.
The corrective path is methodical. Start with the signing key and token validation parameters โ those fixes are low-effort and high-impact. Then address token lifetime and rotation. Then lock down the auth endpoints with rate limiting. Then audit your logging configuration for credential leakage. Treat it as a progression, not a single sprint.
When Broken Authentication Intersects with Other Risks
Broken authentication rarely operates in isolation. It interacts with several other API security risks in ways that amplify the impact:
With BOLA: If authentication is broken and an attacker obtains a valid token for any user โ not just a privileged one โ they can then use object-level authorization flaws to access data belonging to other users. The token provides the initial foothold; BOLA provides the lateral movement.
With Excessive Data Exposure: If token validation is loose and a service accepts tokens not intended for it, it may return responses that include more data than the legitimate caller would ever see โ inadvertently exposing fields that were gated by audience/scope.
With Broken Function Level Authorization: A stolen or forged token that passes weak validation may carry claims that grant access to administrative functions the original user was never meant to reach โ especially in systems where role claims are embedded in the token without server-side verification.
This is why the defence-in-depth checklist matters. Fixing authentication in isolation is necessary but not sufficient for a secure API.
โ Prefer a one-time tip? Buy us a coffee โ every bit helps keep the content coming!
Frequently Asked Questions
What is broken authentication in the context of ASP.NET Core APIs? Broken authentication refers to weaknesses in how an ASP.NET Core API verifies the identity of callers. This includes weak JWT signing keys, incomplete token validation (skipping audience/issuer checks), insecure token transmission over HTTP, long-lived tokens without rotation, and auth endpoints exposed to brute-force or credential stuffing attacks. OWASP classifies this as API2:2023.
How short should JWT access token expiry be in ASP.NET Core? For user-facing APIs, the standard recommendation is 10โ15 minutes. This limits the damage window if a token is intercepted or leaked. Short access tokens should be paired with a refresh token rotation strategy so that users are not forced to re-authenticate constantly โ the refresh token silently issues a new access token before the current one expires.
What happens if I leave ValidateAudience = false in my JWT configuration? Disabling audience validation means a token issued for one service in your system can be used against any other service that trusts the same signing key. In a multi-service architecture, this creates a cross-service token replay risk โ an attacker with a token for a low-privilege service can use it against a higher-privilege one, depending on what role claims are embedded.
Should I set ClockSkew to TimeSpan.Zero in production? Yes, in most cases. The default five-minute clock skew means tokens are accepted for five minutes past their stated expiry. For short-lived access tokens, this represents a 33โ50% extension of the valid window. Setting ClockSkew = TimeSpan.Zero eliminates this, but requires your server clocks to be synchronised (NTP, or cloud-provider time sync โ which is standard in Azure, AWS, and GCP environments).
What is refresh token rotation and why does it matter for ASP.NET Core APIs? Refresh token rotation means that when a refresh token is used to obtain a new access token, the old refresh token is immediately invalidated and a new one is issued. If the old refresh token is presented again (indicating it may have been stolen and used by an attacker), the server detects the replay and revokes the entire token family for that session. This significantly reduces the impact of refresh token theft compared to long-lived static refresh tokens.
How do I prevent JWT secrets from appearing in application logs in ASP.NET Core? Configure your structured logging provider (Serilog, for example) to exclude the Authorization and Cookie headers from request log output. This is typically done through destructuring policies or explicit header exclusions in the UseSerilogRequestLogging() configuration. Also ensure that exception handlers never log the full request body โ a login request body contains plaintext credentials.
Is rate limiting on the login endpoint enough to prevent credential stuffing? Rate limiting raises the cost significantly but is not a complete defence on its own. Effective credential stuffing mitigation combines rate limiting per IP, per username (to detect distributed attacks), temporary account lockout after repeated failures, Retry-After headers on 429 responses, and ideally CAPTCHA or bot detection for web-facing login flows. Each layer makes automated attacks less economical.






