Web API  

Securing REST APIs with API Keys and JWT Tokens in ASP.NET Core

In modern web and mobile applications, securing REST APIs is critical. Unauthorized access can lead to data leaks, account takeovers, or service abuse. Two of the most widely used approaches for API security are API keys and JWT (JSON Web Tokens).

This article explores how to implement both methods in ASP.NET Core, highlighting best practices, real-world implementation patterns, and strategies for production-ready security.

Table of Contents

  1. Introduction to API Security

  2. API Keys vs JWT Tokens

  3. Architectural Considerations

  4. Securing APIs with API Keys

  5. Securing APIs with JWT Tokens

  6. Token Expiration and Refresh

  7. Role-Based Access Control (RBAC) with JWT

  8. Best Practices for Production

  9. Logging and Monitoring

  10. Common Pitfalls

  11. Conclusion

1. Introduction to API Security

REST APIs are stateless interfaces, which makes them scalable but also vulnerable if not secured. Common security threats include:

  • Unauthorized access

  • Man-in-the-middle attacks

  • Replay attacks

  • Credential leaks

API keys and JWT tokens help mitigate these risks by ensuring only authenticated and authorized clients can access the API.

2. API Keys vs JWT Tokens

FeatureAPI KeyJWT Token
TypeSimple static tokenSigned JSON object
Use CaseService-to-service authenticationUser authentication, authorization
ExpirationUsually staticConfigurable, often short-lived
RevocationManualCan use blacklists or short TTL
SecurityLess secure aloneMore secure, can include claims
  • API Keys: Ideal for service-to-service APIs, simple and easy to implement.

  • JWT Tokens: Ideal for user authentication and authorization, supports claims and expiry.

3. Architectural Considerations

When securing APIs:

  1. Use HTTPS: Never transmit tokens or keys over HTTP.

  2. Separate Authentication and Authorization: Authentication verifies identity; authorization checks permissions.

  3. Use Middleware: ASP.NET Core allows adding middleware for token validation and claims extraction.

  4. Centralized Key Management: Avoid hardcoding keys in code.

4. Securing APIs with API Keys

4.1 Generating API Keys

API keys are usually random strings stored securely in a database. Example:

public string GenerateApiKey()
{
    return Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
}

4.2 Storing API Keys

  • Store hashed API keys in the database to avoid leaks.

  • Include metadata like ClientName, CreationDate, ExpiryDate, and Status.

public class ApiKey
{
    public string KeyHash { get; set; }
    public string ClientName { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? ExpiresAt { get; set; }
    public bool IsActive { get; set; }
}

4.3 Validating API Keys in ASP.NET Core

Create a middleware:

public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string API_KEY_HEADER = "X-API-KEY";

    public ApiKeyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, IApiKeyService apiKeyService)
    {
        if (!context.Request.Headers.TryGetValue(API_KEY_HEADER, out var extractedKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("API Key is missing");
            return;
        }

        if (!await apiKeyService.IsValidKeyAsync(extractedKey))
        {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsync("Invalid API Key");
            return;
        }

        await _next(context);
    }
}

Register middleware in Program.cs:

app.UseMiddleware<ApiKeyMiddleware>();

4.4 Best Practices for API Keys

  • Rotate keys periodically.

  • Use rate limiting per key.

  • Restrict IP addresses if possible.

  • Monitor key usage.

5. Securing APIs with JWT Tokens

JWTs are signed tokens that carry claims about the user or client.

5.1 Installing Packages

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

5.2 Configuring JWT Authentication

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});

5.3 Generating JWT Tokens

public string GenerateJwtToken(User user)
{
    var claims = new[]
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim("role", user.Role)
    };

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: _config["Jwt:Issuer"],
        audience: _config["Jwt:Audience"],
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: creds
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
}

5.4 Validating JWT Tokens

Use the [Authorize] attribute on controllers:

[Authorize(Roles = "Admin")]
[HttpGet("admin-data")]
public IActionResult GetAdminData()
{
    return Ok("Secure admin data");
}

ASP.NET Core middleware handles validation automatically.

6. Token Expiration and Refresh

  • JWT tokens should have short expiration (15-60 minutes).

  • Implement refresh tokens for long-lived sessions.

  • Store refresh tokens securely in the database and revoke on logout or suspicious activity.

7. Role-Based Access Control (RBAC) with JWT

  • Add roles as claims in JWT.

  • Use [Authorize(Roles = "Admin,Manager")] for role-based endpoints.

  • Claims can also include permissions, department, or tenant information.

[Authorize(Policy = "CanEditOrders")]
public IActionResult EditOrder(int id)
{
    // Only users with "CanEditOrders" claim can access
    return Ok();
}

8. Best Practices for Production

  1. Always use HTTPS to protect tokens and API keys.

  2. Do not store sensitive data in JWT payloads — only claims necessary for authorization.

  3. Rotate keys and secrets regularly.

  4. Limit API access using IP whitelisting and rate limiting.

  5. Blacklist revoked tokens to prevent misuse.

  6. Use standard libraries for token validation instead of custom code.

9. Logging and Monitoring

  • Log failed authentication attempts.

  • Monitor token usage patterns.

  • Integrate with SIEM tools for suspicious activity detection.

10. Common Pitfalls

  • Using JWT tokens without expiration.

  • Hardcoding API keys in source code.

  • Not validating token signatures.

  • Storing sensitive user data in JWT payloads.

  • Not implementing proper revocation and rotation strategies.

Conclusion

Securing REST APIs with API keys and JWT tokens is essential for modern applications.

  • API keys are simple and suitable for service-to-service authentication.

  • JWT tokens provide flexible, claim-based authentication and authorization for users.

By following best practices, implementing proper token validation, expiration, and monitoring, developers can build APIs that are secure, maintainable, and production-ready.

Key Takeaways for Senior Developers

  • Prefer JWT for user authentication and claims-based authorization.

  • Use API keys for server-to-server communication with rate limiting.

  • Always use HTTPS and rotate secrets periodically.

  • Validate tokens using standard libraries and never trust client input.

  • Implement RBAC and claims for fine-grained access control.