ASP.NET Core  

ASP.NET Core Authentication – JWT vs API Key (Correct Enterprise Approach)

Handling Internal, External, and Hybrid API Access the Right Way

In real-world ASP.NET Core applications, not all APIs are meant for the same type of consumers.

Some APIs are:

  • Used only by internal applications (UI, mobile apps, admin portals)

  • Used only by external systems (third-party apps, cron jobs, integrations)

  • Used by both internal and external consumers

Using a single authentication mechanism for all APIs often leads to:

  • Security loopholes

  • Broken authentication

  • Hard-to-maintain code

This article demonstrates the correct and scalable approach to handle:

  1. JWT-only access (Internal applications)

  2. API Key–only access (External applications)

  3. JWT OR API Key access (Hybrid APIs)

All implemented using ASP.NET Core Authentication Handlers and Policies — the recommended enterprise pattern.

Why NOT Use a Single Custom Authentication Attribute?

A common mistake is:

  • Creating one custom filter

  • Checking JWT and API key manually

  • Setting it as the default scheme

This approach breaks because:

  • JWT middleware is bypassed

  • [Authorize] stops working correctly

  • Debugging becomes painful

Authentication Types – When to Use What

AuthenticationUsed ByBest For
JWT (Bearer Token)Internal appsUI, Mobile, Admin portals
API Key (Header-based)External appsIntegrations, services
JWT OR API KeyBothShared APIs

Authentication Schemes

public static class AuthSchemes
{
    public const string Jwt = JwtBearerDefaults.AuthenticationScheme;
    public const string ApiKey = "ApiKey";
}

API Key Authentication Handler (External Systems)

public class ApiKeyAuthHandler 
    : AuthenticationHandler<AuthenticationSchemeOptions>
{
    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue("X-Api-Key", out var apiKey))
            return Task.FromResult(AuthenticateResult.NoResult());

        if (apiKey != "MY_STATIC_API_KEY_123")
            return Task.FromResult(AuthenticateResult.Fail("Invalid API Key"));

        var claims = new[]
        {
            new Claim(ClaimTypes.Name, "ExternalSystem"),
            new Claim("AccessedBy", "ApiKey")
        };

        var identity = new ClaimsIdentity(claims, AuthSchemes.ApiKey);
        var principal = new ClaimsPrincipal(identity);

        return Task.FromResult(
            AuthenticateResult.Success(
                new AuthenticationTicket(principal, AuthSchemes.ApiKey)));
    }
}

JWT Authentication (Internal Applications)

JWT is used for logged-in users.

  • Token generated after login

  • Sent in Authorization: Bearer <token>

  • Validated by ASP.NET Core middleware

builder.Services.AddAuthentication()
    .AddJwtBearer(AuthSchemes.Jwt, options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey =
                new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes("SUPER_SECRET_KEY"))
        };
    })
    .AddScheme<AuthenticationSchemeOptions, ApiKeyAuthHandler>(
        AuthSchemes.ApiKey, null);

Authorization Policy (JWT OR API Key)

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("JwtOrApiKey", policy =>
    {
        policy.AddAuthenticationSchemes(
            AuthSchemes.Jwt,
            AuthSchemes.ApiKey);

        policy.RequireAuthenticatedUser();
    });
});

Login API – JWT Token Generation (Static User)

[HttpPost("login")]
public IActionResult Login(LoginRequest request)
{
    if (request.Username != "admin" || request.Password != "password")
        return Unauthorized();

    var claims = new[]
    {
        new Claim(ClaimTypes.Name, request.Username),
        new Claim("AccessedBy", "Jwt")
    };

    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes("SUPER_SECRET_KEY"));

    var token = new JwtSecurityToken(
        claims: claims,
        expires: DateTime.UtcNow.AddMinutes(30),
        signingCredentials:
            new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
    );

    return Ok(new
    {
        access_token = new JwtSecurityTokenHandler().WriteToken(token)
    });
}

CheckApiController – All 3 Scenarios

[ApiController]
[Route("api/check")]
public class CheckApiController : ControllerBase
{
    private readonly ILogger<CheckApiController> _logger;

    public CheckApiController(ILogger<CheckApiController> logger)
    {
        _logger = logger;
    }

    // CASE 1: Internal apps only (JWT)
    [HttpGet("internal")]
    [Authorize(AuthenticationSchemes = AuthSchemes.Jwt)]
    public IActionResult InternalOnly()
    {
        LogAccess();
        return Ok("Accessible only with JWT");
    }

    // CASE 2: External apps only (API Key)
    [HttpGet("external")]
    [Authorize(AuthenticationSchemes = AuthSchemes.ApiKey)]
    public IActionResult ExternalOnly()
    {
        LogAccess();
        return Ok("Accessible only with API Key");
    }

    // CASE 3: Internal OR External
    [HttpGet("hybrid")]
    [Authorize(Policy = "JwtOrApiKey")]
    public IActionResult Hybrid()
    {
        LogAccess();
        return Ok("Accessible with JWT or API Key");
    }

    private void LogAccess()
    {
        var accessType = User.Claims
            .FirstOrDefault(c => c.Type == "AccessedBy")?.Value;

        _logger.LogInformation(
            "API accessed using {AccessType}", accessType);
    }
}

How to Call APIs

JWT (Internal)

Authorization: Bearer <JWT_TOKEN>

API Key (External)

X-Api-Key: MY_STATIC_API_KEY_123

Postman Request JWT (Internal)

Internal_Call_SP

Postman Request API Key (External)

External_Call_SP

Postman Request Hybrid By JWT (Internal)

Hybrid_Call_SP_JWT

Postman Request Hybrid By API Key (External)

Hybrid_Call_SP_API_Key

When to Use Which Authentication?

Use JWT When:

  • User login is required

  • Role/claim-based authorization is needed

  • UI or mobile apps consume APIs

Use API Key When:

  • No user context exists

  • System-to-system communication

  • External integrations

Use Both When:

  • Same API exposed internally and externally

  • Different consumers, same functionality

Conclusion

Supporting JWT and API Key authentication together is a very common enterprise requirement.

The correct ASP.NET Core approach is:

  • Use separate authentication schemes

  • Control access via policies

  • Avoid custom authorization filters

  • Keep controllers clean with [Authorize]

Source code is attached for your reference.