Introduction to Token Security

Welcome back! In the previous lesson, we established the foundation of our authentication system by implementing and rotating refresh tokens in a C# ASP.NET Core application. While rotation is a powerful security feature, it is not a silver bullet. If an attacker steals a valid refresh token, they can potentially maintain access until that token is rotated or expires.

In this lesson, we will focus on detection. We will build an intelligent system that monitors how tokens are used, identifies suspicious patterns—such as a token being used from two different countries simultaneously—and automatically revokes access when theft is suspected.

Token Security Fundamentals

Token security encompasses the strategies and code logic used to protect authentication artifacts from unauthorized use. In modern stateless architectures, tokens are effectively the "keys to the kingdom." Unlike session-based authentication where the server holds the state, a token is self-contained. If an attacker possesses it, they can impersonate the user without needing the user's password.

Because refresh tokens are long-lived and powerful (they can generate new access tokens repeatedly), they are high-value targets for attackers. Therefore, we must implement "defense in depth"—layering rotation, secure storage, and active monitoring to protect user accounts.

Understanding Token Revocation Policies

Before we dive into detection, it is crucial to understand the different approaches to token revocation:

Single-Use Rotation: Each refresh token can only be used once. After use, that specific token is invalidated and a new one is issued. Other tokens from different sessions remain valid. This allows multiple concurrent sessions (e.g., mobile + desktop) while protecting against token replay attacks.

Single-Session Policy: When a refresh token is used, ALL other refresh tokens for that user are immediately revoked. This enforces one active session at a time, providing maximum security but poor user experience—logging in on your phone would forcibly log out your laptop.

Conditional Revocation (Our Approach): This lesson implements a hybrid approach. During normal operation, tokens are rotated individually (single-use pattern). However, when suspicious activity is detected—such as the same token family being used from multiple IP addresses—the system automatically switches to aggressive mode and revokes ALL tokens for that user. This balances security with usability: users can have multiple sessions under normal circumstances, but the system protects them by killing all sessions when compromise is suspected.

Why Security Matters

The stakes are higher with refresh tokens than with short-lived access tokens. If an access token is stolen, it is annoying but temporary; it will expire in minutes. However, a stolen refresh token allows for persistent access. It can persist across sessions, devices, and sometimes even password changes if the revocation logic is not watertight.

We must address several specific risks:

  • Long Lifespans: The window of opportunity for an attacker is much wider.
  • Extended Access: They grant access without requiring user interaction.
  • Scalability vs. Control: While we want the system to be stateless for performance, we need just enough state (logging) to detect anomalies.
Authentication Trade-offs

When building this system, it is helpful to understand the architectural trade-offs we are making. Token-based authentication offers significant advantages but introduces specific complexities we must manage.

Pros:

  • Stateless Scalability: The server does not need to hold a session for every active user, reducing memory overhead.
  • Performance: Fewer database lookups are required for standard API requests (using Access Tokens).
  • Cross-Domain Support: Tokens work well across different domains and microservices.

Cons:

  • Revocation Complexity: It is harder to "kill" a session immediately compared to server-side sessions.
  • Theft Vulnerability: Stolen tokens are valid until expiry or revocation.
  • Implementation Effort: Safe token management requires robust rotation and detection logic.
Tracking Token Usage

To detect theft, we must first observe normal behavior. We cannot detect an anomaly if we do not have a baseline. We will implement a RefreshLog model, which acts as a security journal. Every time a refresh token is presented to our API, we record the context of that request.

Here is the data model we will use to create this audit trail:

This model captures the "Who" (UserId), "What" (Token), "When" (UsedAtUtc), and crucially, the "Where" and "How" (IpAddress and UserAgent). The Successful flag lets us distinguish between valid refresh attempts and failed ones — a high rate of failed attempts for a user is itself a suspicious signal.

Next, we register this model in our AppDbContext so Entity Framework Core can manage the table.

Helper Functions

With the data layer ready, we need utility functions that our endpoints will use. In Minimal API applications, we define these as static helper functions in Program.cs rather than in a controller class. This keeps the code straightforward and consistent with how ASP.NET Core Minimal APIs are structured.

First, we need a helper to reliably extract the client's IP address. If your API sits behind a load balancer (like Nginx or AWS ALB), the direct connection IP will be that of the load balancer, not the user. The X-Forwarded-For header contains the original client IP in these cases.

Analyzing Usage Patterns

Before we implement the refresh endpoint, we need a helper function to analyze the logs. This function queries the history of a specific user to determine their "Risk Level."

The logic here is heuristic-based. We look for:

  1. IP Velocity: Is the user accessing the system from many different IP addresses in a short time?
  2. Device Consistency: Are they switching User Agents frequently?
  3. Failure Rates: Are there many failed attempts?

We define this as a static helper function that our endpoints can call:

Detecting Theft During Refresh

This is the core of our defense. When a client requests a token refresh, we do not simply check if the token is valid. We also check the context of the request against recent history.

Important Note on Production Deployments: The detection logic implemented here uses a simplified heuristic suitable for learning the concepts. It examines the last 5 successful refresh attempts regardless of time window. This approach has a high false-positive rate in real-world scenarios—a user who legitimately switches between home, office, and mobile networks over several days would trigger theft detection unnecessarily.

For production systems, you should implement more sophisticated detection strategies:

Time-Based Velocity Checks: Instead of looking at the last N logs, examine activity within a narrow time window (e.g., 15 minutes). A token being used from London and then Tokyo within minutes is suspicious; the same pattern over days is normal travel.

Token Reuse Detection: Track which specific tokens have already been used and rotated. If a previously-rotated token (one that should be "dead") is presented again, this is a strong theft indicator. Someone is replaying an old token, which is clear evidence of compromise. This is far more reliable than IP analysis alone.

Behavioral Baselines: Build per-user profiles over time. If a user normally accesses from 2-3 IPs (home, work, phone carrier), seeing a 4th from a different country is anomalous. If they regularly travel internationally, it is expected.

With these caveats in mind, let's examine our educational implementation that demonstrates the detection pattern:

The logic flow is as follows:

  1. Capture Context: Get the IP and User-Agent from the incoming HTTP request.
  2. Log Immediately: Create an audit record before validation. Even failed attempts provide valuable security data.
  3. Validate: Check if the token exists and is not expired.
  4. Detect Anomalies: Query recent logs. If the same token family is being used from multiple IPs concurrently, this is a strong indicator of theft (Cookie hijacking or token exfiltration).
  5. Act: If theft is suspected, revoke all tokens for that user immediately to sever the attacker's access. Note that this is different from normal operation—under normal circumstances, only the used token would be rotated. This aggressive revocation only occurs when suspicious patterns are detected.
Administrative Insights

Finally, we expose the analysis logic via an endpoint. This allows customer support or security teams to manually investigate a user account if a customer complains about being logged out or suspects a hack.

Verifying Detection

To ensure our security measures actually work, we need to simulate an attack. We will write a simple test using HttpClient. This test replicates a "Token Replay" attack where a valid token is stolen and used from a different IP address.

The test performs the following steps:

  1. Normal Usage: A user refreshes their token from "Home" (IP A).
  2. Attack: An attacker tries to use that same token from "Unknown Location" (IP B).
  3. Verification: The system should reject the attacker and revoke the user's access to prevent further damage.

We simulate different IP addresses by setting the X-Forwarded-For header on each request:

Sample Test Output

When you run this test, the output demonstrates the system recognizing the anomaly. The change in IP address for the same token family triggers the detection logic in our refresh endpoint.

This output confirms that the API successfully identified that the second request, coming from a different IP address than the recent history suggested, was suspicious. Consequently, it blocked the request and triggered a full revocation.

Best Practices

While the implementation above significantly hardens your API, security is an evolving landscape. Keep these best practices in mind:

  • Log Retention: Don't keep logs forever. Implement a cleanup job to delete RefreshLogs older than 30 or 60 days to manage database size and respect user privacy.
  • Geo-Location: In a production app, resolve IP addresses to Countries or Cities. Blocking a token moving from "London" to "New York" in 5 minutes is more accurate than just checking raw IP strings.
  • User Notification: When theft is detected, send an email to the user: "We detected suspicious activity and logged you out. Please change your password."
Summary and Conclusion

In this lesson, we moved beyond simple token rotation and implemented an active defense system. We created a detailed audit trail using Entity Framework Core, designed heuristic logic to spot anomalies such as IP hopping, and built an automated response mechanism that prioritizes security by revoking compromised sessions.

Critically, our implementation uses a conditional revocation policy: during normal operation, users can maintain multiple concurrent sessions across devices. However, when suspicious patterns emerge—such as rapid IP changes—the system automatically escalates its response and revokes all sessions to protect the user. This balances security with usability better than a strict single-session approach.

You now have a robust system that not only manages the lifecycle of tokens but actively protects them from abuse. These patterns form the backbone of modern, secure authentication systems in ASP.NET Core. In the next stage of the course, we will apply these concepts in a hands-on lab environment.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal