Introduction To Token Blacklisting

Welcome to the lesson on Blacklisting and Forced Revocation in a C# ASP.NET Core REST API. While JSON Web Tokens (JWTs) are stateless by design—improving scalability by removing the need for server-side session storage—this statelessness creates a security gap: once a token is issued, it remains valid until it expires.

In this lesson, we will implement token blacklisting, a hybrid approach that reintroduces a small amount of state to strictly control security. We will build a system that allows administrators to force the immediate invalidation of access tokens, ensuring that compromised or deactivated users lose access instantly.

Understanding Token Blacklisting

Token blacklisting is a security pattern used to bridge the gap between stateless authentication and immediate security control. In a purely stateless JWT system, the server verifies the signature and expiration of a token but does not check a database to see if the token is still allowed.

Blacklisting changes this behavior by maintaining a "deny list" of specific tokens that have been flagged as invalid. When a request arrives, the server performs the standard signature verification and then checks this list. If the token is found on the list, the request is rejected immediately, regardless of the token's mathematical validity.

Defining Forced Revocation

Forced revocation is the deliberate action of invalidating a token before its natural expiration time has been reached. While tokens eventually expire on their own, there are critical security scenarios where waiting for natural expiration is unacceptable.

Revocation is the "emergency brake" of your authentication system. It allows you to terminate a session immediately in response to specific events, such as a user clicking "Log Out on All Devices," an administrator banning a user, or the detection of suspicious activity associated with a specific token.

Comparing Invalidation Strategies

There are three primary architectural approaches to handling token invalidation, each with distinct trade-offs regarding performance and storage.

  1. Blacklisting: You store only the tokens you want to block. This keeps the database table small and lookups fast. It is the preferred method for JWTs because it preserves the benefits of statelessness for the vast majority of "good" traffic.
  2. Token Removal: If you persist all active reference tokens in a database, you can simply delete the token row to invalidate it. This is simple but requires a database lookup for every API request to ensure the token exists, which defeats the scalability purpose of using JWTs.
  3. Direct Invalidation: This involves adding a IsValid boolean flag to a stored token record. Like token removal, this forces a database dependency for every single authentication attempt, adding significant latency to your API.

For this course, we will implement the Blacklisting approach. It offers the best balance: we accept a database lookup only for the specific purpose of security checks, and the table naturally cleans itself up as tokens expire.

Determining Usage Scenarios

Implementing a blacklist adds complexity to your application, so it is important to understand exactly when this mechanism should be triggered.

These techniques are required in the following scenarios:

  • Logout Events: When a user logs out, their JWT is still valid in the wild. Blacklisting ensures it cannot be reused.
  • Security Breaches: If a token is suspected of being stolen, it must be killed immediately.
  • Account Suspension: When an admin bans a user, their current active session must end instantly.
  • Policy Enforcement: When user permissions change drastically, old tokens with outdated claims must be revoked to force a refresh.
Weighing Pros And Cons

Before writing code, it is valuable to assess the architectural impact of adding a blacklist to your authentication pipeline.

Pros:

  • Immediate Response: You can stop an active attack or enforce a ban in milliseconds.
  • Auditability: The blacklist provides a historical record of security interventions.
  • Granular Control: You can revoke specific tokens without invalidating an entire user account or rotating signing keys.

Cons:

  • Database Dependency: Validation is no longer purely CPU-bound; it requires an I/O operation (network/disk).
  • Latency: Every authenticated request incurs a slight delay due to the blacklist check.
  • Maintenance: You must manage the storage of blacklisted tokens, though this can be automated with expiration policies.
The TokenBlacklistEntry Model

The foundation of our implementation is the TokenBlacklistEntry entity. This model represents a single entry in our deny list — one revoked token.

We use a separate Guid Id as the primary key, following Entity Framework Core conventions (EF Core automatically treats a property named Id as the primary key). The Token string is stored as a separate column that we query against. This separation keeps the primary key stable and predictable while allowing the token value itself to be any format we choose.

The ExpiresAtUtc field is critical for housekeeping: it records when the JWT itself would naturally expire, allowing background cleanup jobs to periodically remove old entries. Once a token has passed its natural expiration, there is no need to keep it in the blacklist — standard JWT validation would reject it anyway.

To integrate this into your database, we register the entity in the AppDbContext:

The Revocation Service

We need a dedicated service to handle the logic of adding tokens to the blacklist and checking whether a given token has been revoked. This TokenBlacklistService encapsulates all blacklist operations behind a clean API.

The RevokeAsync method decodes the JWT to extract its expiration date, ensuring the blacklist entry doesn't persist forever. We read the token without fully validating its signature because we might want to blacklist a token that is already technically invalid or malformed, simply to ensure it is blocked from our system.

The IsRevokedAsync method checks whether a token exists in the blacklist and is still within its expiration window. It returns a tuple containing both the revocation status and the reason, which can be included in error responses for debugging.

Register this service in your dependency injection container in . We also register the middleware class (covered in the next section) as a scoped service, since it implements the interface:

The Validation Middleware

In ASP.NET Core, the cleanest way to intercept every request is via middleware. We will create a custom middleware component that sits in the request pipeline. It inspects the HTTP headers, extracts the token, and asks our service if that token is blacklisted.

We implement the IMiddleware interface, which allows the middleware to receive scoped dependencies (like our TokenBlacklistService) through constructor injection. This is the "factory-activated" middleware pattern — ASP.NET Core resolves a new instance from DI for each request.

This middleware effectively acts as a gatekeeper. If the token is blacklisted, the request is short-circuited — meaning the pipeline stops immediately, and the controller is never reached.

The order in which you register middleware in Program.cs is critical. This middleware should run after UseAuthentication (so the JWT has been parsed and the user identity established) but (so we block access before permissions are checked). This ensures that a blacklisted token is rejected as early as possible in the pipeline.

The Admin Endpoint

To make this functionality accessible, we expose an API endpoint that Administrators can call. This endpoint accepts a token to revoke and an optional reason, then delegates to our TokenBlacklistService.

We first define a Data Transfer Object (DTO) for the request body using a C# record for concise, immutable typing:

Next, we implement the endpoint using Minimal APIs. We protect this endpoint with role-based authorization to prevent regular users (or attackers) from revoking each other's tokens:

Verifying The Implementation

When you successfully call the revocation endpoint, the API will return a JSON response confirming the action. This confirms that the token has been parsed, its expiration extracted, and the record saved to the database.

Here is an example of the JSON output you would receive after a successful revocation request:

Understanding the Output:

  • message: Confirms the operation succeeded.
  • token: A truncated preview of the revoked token, useful for verifying you targeted the correct one without exposing the full token in logs or responses.

Any subsequent attempt to use this specific token to access a protected resource will now result in a 403 Forbidden response with the error Access token has been revoked, along with the reason provided during revocation.

Summary And Next Steps

In this lesson, we have successfully implemented a robust token blacklisting mechanism. You now have a system that respects the stateless nature of JWTs for the majority of requests while providing a powerful "kill switch" for specific security events.

We covered the creation of a specialized database model (TokenBlacklistEntry) for storing revoked tokens, a service (TokenBlacklistService) to manage revocation and lookup logic, and custom middleware (BlacklistMiddleware) to intercept and validate requests against the blacklist. This architecture provides a necessary layer of defense for modern REST APIs, allowing you to react instantly to security threats.

In the upcoming practice exercises, you will apply these concepts directly. You will be tasked with building the model, implementing the revocation service, wiring up the middleware, and creating the admin endpoint to see these protections in action.

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