Introduction Link to heading
Keycloak, OAuth2 Proxy, and ASP.NET Core Web API work together to secure your web application. Let’s dive into how these components interact using a mermaid sequence diagram.
sequenceDiagram
participant User
participant Keycloak
participant OAuth2 Proxy
participant ASP.NET Core Web API
User -> Keycloak: Login
Keycloak -> User: Redirect to OAuth2 Proxy
User -> OAuth2 Proxy: Request
OAuth2 Proxy -> ASP.NET Core Web API: Forward Request
ASP.NET Core Web API -> OAuth2 Proxy: Get Roles from Headers
OAuth2 Proxy -> User: Response
OAuth2 Proxy Configuration OAuth2 Proxy forwards headers to the upstream service by configuring:
pass_access_token = true
pass_authorization_header = true
Get access token from headers in ASP.NET Core Web API Link to heading
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public CustomAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder) : base(options, logger, encoder)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Try to get the token from the header
if (!Request.Headers.TryGetValue("x-forwarded-access-token", out var token))
{
return Task.FromResult(AuthenticateResult.Fail("Header Not Found."));
}
try
{
// Attempt to decode the JWT token
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadJwtToken(token);
var claims = jwtToken.Claims.Select(claim => new Claim(claim.Type, claim.Value)).ToList();
var identity = new ClaimsIdentity(claims, Scheme.Name);
var resourceAccessClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == "resource_access")?.Value;
if (resourceAccessClaim != null)
{
var resourceAccessData = JsonSerializer.Deserialize<Dictionary<string, object>>(resourceAccessClaim);
if (resourceAccessData != null && resourceAccessData.TryGetValue("<your_realm_client_name_here>", out var expressMiddlewareData))
{
var expressMiddlewareDict = JsonSerializer.Deserialize<Dictionary<string, object>>(expressMiddlewareData.ToString());
if (expressMiddlewareDict != null && expressMiddlewareDict.TryGetValue("roles", out var roles))
{
var parsedRoles = JsonSerializer.Deserialize<List<string>>(roles.ToString());
if (parsedRoles != null)
{
foreach (var role in parsedRoles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
}
}
}
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
catch
{
return Task.FromResult(AuthenticateResult.Fail("Invalid Token."));
}
}
}
then you can config authentication and authorization
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// use custom authentication schema
builder.Services.AddAuthentication("CustomScheme").AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("CustomScheme", options => { });
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
now you can protect you controller or action like this:
[ApiController]
[Authorize(AuthenticationSchemes = "CustomScheme")]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IHttpContextAccessor _httpContextAccessor;
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger, IHttpContextAccessor httpContextAccessor)
{
_logger = logger;
_httpContextAccessor = httpContextAccessor;
}
[Authorize(Roles = "admin")]
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
Summary Link to heading
Pros: Link to heading
- Enhanced security with Keycloak and OAuth2 Proxy
- Centralized authentication and authorization management
- Seamless integration with ASP.NET Core Web API
Cons: Link to heading
- Increased complexity in setup and configuration
- Potential performance overhead due to additional layers
- Dependency on external services for authentication and authorization
By leveraging Keycloak, OAuth2 Proxy, and ASP.NET Core Web API, you can create a robust and secure web application environment.