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."));

                // 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));
                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.

            // Learn more about configuring Swagger/OpenAPI at
            // use custom authentication schema
            builder.Services.AddAuthentication("CustomScheme").AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("CustomScheme", options => { });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())




now you can protect you controller or action like this:

    [Authorize(AuthenticationSchemes = "CustomScheme")]
    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)]

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.