ASP.NET Core 8 Web API -

Authentication

Authentication is a critical component of securing your ASP.NET Core 8 Web API. It ensures that users are who they claim to be and grants them access to resources based on their identity. This guide covers various authentication methods, including JWT (JSON Web Token), OAuth2, and OpenID Connect, and provides detailed examples and best practices.


1. Introduction to Authentication

Authentication is the process of verifying the identity of a user or service. In ASP.NET Core, authentication is typically implemented using middleware and involves validating credentials and issuing tokens or cookies.


2. JWT Authentication

2.1 Install JWT Bearer Authentication
        
            
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

        
    


2.2 Configure JWT Authentication in Program.cs
        
            
var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();

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"]))
    };
});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

        
    


2.3 Add JWT Configuration to appsettings.json
        
            
{
  "Jwt": {
    "Issuer": "yourissuer",
    "Audience": "youraudience",
    "Key": "your_secret_key_which_is_longer_than_16_characters"
  }
}

        
    


2.4 Generate JWT Token
        
            
public class AuthService
{
    private readonly IConfiguration _configuration;

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

    public string GenerateJwtToken(string username)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

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

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: credentials
        );

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

        
    


2.5 Protecting an Endpoint
        
            
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetProducts()
    {
        var products = new List<string> { "Product1", "Product2", "Product3" };
        return Ok(products);
    }
}

        
    

3. OAuth2 and OpenID Connect

OAuth2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service. OpenID Connect is an identity layer on top of OAuth2 that verifies the identity of users.

3.1 Add OAuth2 and OpenID Connect Authentication
        
            dotnet add package Microsoft.Identity.Web
        
    


3.2 Configure OAuth2 and OpenID Connect in Program.cs
        
            var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();
        
    


3.3 Add Azure AD Configuration to appsettings.json
        
            {
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "yourdomain.com",
    "TenantId": "yourtenantid",
    "ClientId": "yourclientid",
    "ClientSecret": "yourclientsecret"
  }
}
        
    


3.4 Protecting an Endpoint
        
            [Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
    [HttpGet]
    public IActionResult GetSecureData()
    {
        var data = new List<string> { "Secure Data1", "Secure Data2" };
        return Ok(data);
    }
}
        
    

4. Custom Authentication

For custom authentication schemes, you can create a custom authentication handler by inheriting from AuthenticationHandler<TOptions>.

4.1 Create Custom Authentication Handler
        
            public class CustomAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public CustomAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // Your custom authentication logic
        var claims = new[] { new Claim(ClaimTypes.Name, "CustomUser") };
        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}
        
    


4.2 Register Custom Authentication Scheme in Program.cs
        
            var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();

builder.Services.AddAuthentication("CustomScheme")
    .AddScheme<AuthenticationSchemeOptions, CustomAuthHandler>("CustomScheme", options => {});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();
        
    


4.3 Protecting an Endpoint
        
            [Authorize(AuthenticationSchemes = "CustomScheme")]
[ApiController]
[Route("api/[controller]")]
public class CustomAuthController : ControllerBase
{
    [HttpGet]
    public IActionResult GetCustomAuthData()
    {
        var data = new List<string> { "Custom Auth Data1", "Custom Auth Data2" };
        return Ok(data);
    }
}
        
    

5. Securing API Keys

API keys are a simple way to secure your API. They are less secure than OAuth2 or OpenID Connect but can be useful for simple use cases.

5.1 Create API Key Authentication Handler
        
            public class ApiKeyAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private const string ApiKeyHeaderName = "X-API-KEY";
    private readonly IConfiguration _configuration;

    public ApiKeyAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IConfiguration configuration)
        : base(options, logger, encoder, clock)
    {
        _configuration = configuration;
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKey))
        {
            return Task.FromResult(AuthenticateResult.Fail("API Key not found"));
        }

        var configuredApiKey = _configuration.GetValue<string>("ApiKey");
        if (apiKey != configuredApiKey)
        {
            return Task.FromResult(AuthenticateResult.Fail("Invalid API Key"));
        }

        var claims = new[] { new Claim(ClaimTypes.Name, "ApiKeyUser") };
        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}
        
    


5.2 Register API Key Authentication Scheme in Program.cs
        
            var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();

builder.Services.AddAuthentication("ApiKeyScheme")
    .AddScheme<AuthenticationSchemeOptions, ApiKeyAuthHandler>("ApiKeyScheme", options => {});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();
        
    


5.3 Add API Key Configuration to appsettings.json
        
            {
  "ApiKey": "your_api_key"
}
        
    


5.4 Protecting an Endpoint
        
            [Authorize(AuthenticationSchemes = "ApiKeyScheme")]
[ApiController]
[Route("api/[controller]")]
public class ApiKeyController : ControllerBase
{
    [HttpGet]
    public IActionResult GetApiKeyData()
    {
        var data = new List<string> { "API Key Data1", "API Key Data2" };
        return Ok(data);
    }
}
        
    

6. Best Practices for Authentication


Comprehensive Example

Here is a comprehensive example combining JWT authentication, OAuth2 and OpenID Connect, custom authentication, and API key authentication.

Program.cs
        
            using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();

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"]))
    };
})
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.AddScheme<AuthenticationSchemeOptions, CustomAuthHandler>("CustomScheme", options => {})
.AddScheme<AuthenticationSchemeOptions, ApiKeyAuthHandler>("ApiKeyScheme", options => {});

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();
        
    


AuthService
        
            
public class AuthService
{
    private readonly IConfiguration _configuration;

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

    public string GenerateJwtToken(string username, string role)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

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

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: credentials
        );

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

        
    


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

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

    [HttpGet("{id}")]
    public IActionResult GetProduct(int id)
    {
        using (_logger.BeginScope("Getting product with id {ProductId}", id))
        {
            _logger.LogInformation("Fetching product");

            // Simulate fetching product
            var product = new Product { Id = id, Name = "Sample Product", Price = 9.99m };

            if (product == null)
            {
                _logger.LogWarning("Product with id {ProductId} not found", id);
                return NotFound();
            }

            _logger.LogInformation("Returning product with id {ProductId}", id);
            return Ok(product);
        }
    }

    [HttpPost]
    public IActionResult CreateProduct([FromBody] Product product)
    {
        if (!ModelState.IsValid)
        {
            _logger.LogWarning("Invalid product model received");
            return BadRequest(ModelState);
        }

        _logger.LogInformation("Creating a new product: {@Product}", product);

        // Simulate saving product
        product.Id = new Random().Next(1, 1000);

        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }
}

        
    


Product Model
        
            
public class Product
{
    public int Id { get; set; }

    [Required(ErrorMessage = "Name is required")]
    [StringLength(100, ErrorMessage = "Name can't be longer than 100 characters")]
    public string Name { get; set; }

    [Range(0.01, 1000.00, ErrorMessage = "Price must be between 0.01 and 1000.00")]
    public decimal Price { get; set; }
}

        
    

Conclusion

Authentication is a critical component of securing your ASP.NET Core 8 Web API. By implementing JWT, OAuth2, OpenID Connect, and custom authentication schemes, you can ensure that only authorized users can access your resources. Following best practices such as using secure tokens, keeping secrets safe, and logging authentication events will help you build secure and reliable web APIs. This comprehensive guide provides the tools and knowledge to implement effective authentication in your ASP.NET Core 8 Web API projects.