DEV Community

Cover image for Securing a .NET API C#: API Key, Basic Authentication, and JWT πŸ”’
Zied Rebhi
Zied Rebhi

Posted on

Securing a .NET API C#: API Key, Basic Authentication, and JWT πŸ”’

When developing a .NET API, ensuring secure access control is critical to protect sensitive data and resources. Several methods are available, including API Key Authentication, Basic Authentication, and JSON Web Tokens (JWT). Each has its use case and security implications. This article explores how to implement these methods in a .NET API.

Sample Payment API

Here is a basic example of a Payment API with CRUD operations:
You can find the full code in this GitHub Repo

[Route("api/[controller]")]
    [ApiController]
    public class PaymentsController : ControllerBase
    {
        // Get all payments
        [HttpGet]
        public ActionResult<IEnumerable<Payment>> GetPayments()
        {
            var payments = PaymentRepository.GetPayments();
            return Ok(payments);
        }

        // Get a payment by ID
        [HttpGet("{id}")]
        public ActionResult<Payment> GetPayment(int id)
        {
            var payment = PaymentRepository.GetPaymentById(id);
            if (payment == null)
                return NotFound();
            return Ok(payment);
        }

        // Add a new payment
        [HttpPost]
        public ActionResult<Payment> PostPayment([FromBody] Payment payment)
        {
            if (payment == null)
                return BadRequest();

            payment.Id = PaymentRepository.GetPayments().Max(p => p.Id) + 1;  // Auto-increment ID
            PaymentRepository.AddPayment(payment);
            return CreatedAtAction(nameof(GetPayment), new { id = payment.Id }, payment);
        }

        // Update an existing payment
        [HttpPut("{id}")]
        public IActionResult PutPayment(int id, [FromBody] Payment payment)
        {
            if (payment == null)
                return BadRequest();

            PaymentRepository.UpdatePayment(id, payment);
            return NoContent();
        }

        // Delete a payment
        [HttpDelete("{id}")]
        public IActionResult DeletePayment(int id)
        {
            var payment = PaymentRepository.GetPaymentById(id);
            if (payment == null)
                return NotFound();

            PaymentRepository.DeletePayment(id);
            return NoContent();
        }
    }
Enter fullscreen mode Exit fullscreen mode

1. API Key Authentication

API Key Authentication is a simple mechanism where clients send a unique key with their requests. The server validates this key to grant access.

Required Package

No additional package is required for API Key Authentication.

Implementation:

  • Generate an API Key:

Create a secure and unique key for each client. Store these keys securely in your environment variables or app Settings etc to get it .

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiKey": "SuperSecretApiKey12345"
}

Enter fullscreen mode Exit fullscreen mode
  • Middleware for Validation:

Add middleware to validate the API key in incoming requests.

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

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

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

            var apiKey = configuration.GetValue<string>("ApiKey");

            if (!apiKey.Equals(extractedApiKey))
            {
                context.Response.StatusCode = 403;
                await context.Response.WriteAsync("Unauthorized client.");
                return;
            }

            await _next(context);
        }
    }

    public static class ApiKeyMiddlewareExtensions
    {
        public static IApplicationBuilder UseApiKeyMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ApiKeyMiddleware>();
        }
    }

Enter fullscreen mode Exit fullscreen mode
  • Register Middleware: In your program.cs file :
app.UseHttpsRedirection();
app.UseAuthorization();
// Use API Key 
app.UseApiKeyMiddleware(); //add this
app.MapControllers();

Enter fullscreen mode Exit fullscreen mode
  • Swagger Configuration:

To let your clients test the secured API throw Swagger , you should configure it :

// Add Swagger with API Key configuration
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Payment API - API Key", Version = "v1" });
    c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
    {
        Name = "X-API-KEY",
        Type = SecuritySchemeType.ApiKey,
        In = ParameterLocation.Header,
        Description = "Enter your API Key in the header."
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "ApiKey"
                }
            },
            new string[] { }
        }
    });
});
var app = builder.Build();
Enter fullscreen mode Exit fullscreen mode

API Key
When Api key is valid , you will be authorized to use the Api's endpoints else you will get 401 error

API Key


2. Basic Authentication

Basic Authentication requires the client to send a username and password in the Authorization header. This method is suitable for internal APIs or development environments.

Required Package

Install the following NuGet package:

Microsoft.AspNetCore.Authentication
Enter fullscreen mode Exit fullscreen mode

Implementation:

  • Create Authentication Handler:
public class BasicAuthMiddleware
    {
        private readonly RequestDelegate _next;

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

        public async Task InvokeAsync(HttpContext context)
        {
            if (!context.Request.Headers.TryGetValue("Authorization", out var authorizationHeader))
            {
                context.Response.StatusCode = 401;
                context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"PaymentApi\"";
                await context.Response.WriteAsync("Authorization header is missing.");
                return;
            }

            var authHeader = AuthenticationHeaderValue.Parse(authorizationHeader);
            var credentialBytes = Convert.FromBase64String(authHeader.Parameter ?? string.Empty);
            var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':', 2);

            if (credentials.Length != 2 || credentials[0] != "test" || credentials[1] != "test")
            {
                context.Response.StatusCode = 403;
                await context.Response.WriteAsync("Invalid username or password.");
                return;
            }

            await _next(context);
        }
    }

    public static class BasicAuthMiddlewareExtensions
    {
        public static IApplicationBuilder UseBasicAuthMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<BasicAuthMiddleware>();
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Apply Authentication: In program.cs :

app.UseHttpsRedirection();
app.UseAuthorization();
//Use Basic Auth Middleware
app.UseBasicAuthMiddleware();
app.MapControllers();
app.Run();

Enter fullscreen mode Exit fullscreen mode
  • Swagger Configuration:
// Add Swagger with Basic Authentication configuration
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Payment API - Basic Auth", Version = "v1" });
    c.AddSecurityDefinition("Basic", new OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        Scheme = "basic",
        In = ParameterLocation.Header,
        Description = "Enter your username and password as Base64 encoded value in the format 'username:password'."
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Basic"
                }
            },
            new string[] { }
        }
    });
});


var app = builder.Build();
Enter fullscreen mode Exit fullscreen mode

When you run your API :

Basic Auth

Note that you should change the way how to handle the login process ( secure password etc..)


3. JWT Authentication

JWT (JSON Web Token) is a widely-used method for securing APIs. It provides a compact and self-contained token format that includes user claims and is signed for integrity.
Required Package

Install the following NuGet package:

Microsoft.AspNetCore.Authentication.JwtBearer
Enter fullscreen mode Exit fullscreen mode

Implementation:

  • Add Authentication Controller : In this controller , we handle the way how users login and we generate tokens for them.
[ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly IConfiguration _configuration;

        public AuthController(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        [HttpPost("login")]
        public IActionResult Login([FromBody] LoginRequest login)
        {
            // Validate user credentials (hardcoded for example purposes)
            if (login.Username != "test" || login.Password != "test")
                return Unauthorized("Invalid username or password");

            // Generate the JWT token
            var token = GenerateJwtToken(login.Username);

            return Ok(new { Token = token });
        }

        private string GenerateJwtToken(string username)
        {
            var jwtSettings = _configuration.GetSection("Jwt");

            var claims = new[]
            {
            new Claim(JwtRegisteredClaimNames.Sub, username),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.Role, "Admin")
        };

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

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

            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
Enter fullscreen mode Exit fullscreen mode

User model :

    public class LoginRequest
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
Enter fullscreen mode Exit fullscreen mode
  • Add Authentication Service: In program.cs , we configure the authentication service for our application :
var builder = WebApplication.CreateBuilder(args);

var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]);

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 = jwtSettings["Issuer"],
        ValidAudience = jwtSettings["Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(key)
    };
});
// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

Enter fullscreen mode Exit fullscreen mode
  • Apply Authentication: Let's apply the authentication now in program.cs:
app.UseHttpsRedirection();
// Add Authentication
app.UseAuthentication();
app.UseAuthorization(); //<--
app.MapControllers();
app.Run();
Enter fullscreen mode Exit fullscreen mode
  • Swagger Configuration: Let's configure our swagger so it shows the Authorize button using Jwt
// Add Swagger with JWT configuration
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Paymnent API - JWT", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Name = "Authorization",
        Type = SecuritySchemeType.Http,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "Enter 'Bearer' [space] and then your valid token."
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });
});

var app = builder.Build();

Enter fullscreen mode Exit fullscreen mode

In appsettings.json we add the necessary informations:

 "Jwt": {
    "Key": "SuperSecretKey12345SuperSecretKey12345SuperSecretKey12345SuperSecretKey12345",
    "Issuer": "SecureApiJwt",
    "Audience": "ApiUsers",
    "ExpiryMinutes": 60
  }
Enter fullscreen mode Exit fullscreen mode

Don't forget to add [Authorize] in you controllers

To test now , you should Login , you will get a token . In authorize button , you should add the generated token to access to the endpoints

JWT

Conclusion

Securing your .NET API is essential to ensure the integrity and confidentiality of the data it processes. Here's a comparison of the three methods discussed:

comparaison

You can find the full code in my GitHub repo
Next Article will be on how me make our API accessed from other applications throw nuget package and handling the security .

Top comments (0)