Redirect Login Loop with OIDC in ASP.NET Core 8.0 Blazor (Radzen)

Hello everyone :smiley:,

I'm currently facing an redirect loop issue when integrating Keycloak for OpenID Connect (OIDC) authentication in my ASP.NET Core Blazor Server application using Radzen. (Note: When i start the .Server Application, the login works perfectly fine, when starting with Docker i get the redirect issue)

Technologies Involved:

-Keycloak (24.0.4) as the Identity Provider (IDP)
-ASP.NET Core 8.0 for the Backend
-Blazor Server for the Frontend
-Radzen Components for UI
-OpenID Connect (OIDC) for authentication
-HTTPS environment on both the app and the Keycloak Server

Problem Overview:

I have configured Keycloak as the Identity Provider using OIDC in my Blazor Server application. After being redirected to Keycloak for authentication and logging in successfully, I get stuck in an redirect loop between the application and Keycloak which leads to ERR_TOO_MANY_REDIRECTS.

So:

  1. I get redirected to Keycloak for login
  2. After successful login in Keycloak, it redirects me back to my app (/signin-oidc)
  3. The app gets stuck in a loop and keeps redirecting (The Website Title-Tag says "OIDC Form_Post Response, after inspecting the url i noticed, that nonce and state is always changing)

Looks like this in the Docker Desktop Logs:

X-Original-Proto: http
X-Original-For: [::ffff:172.18.0.2]:37404
Request Path: /
Request Method: GET
IsAuthenticated: True
AuthType: AuthenticationTypes.Federation
info: System.Net.Http.HttpClient.SimpleKeycloakAuthServerSample.Server.LogicalHandler[100]
      Start processing HTTP request POST https://testapp.mkw.at/Account/CurrentUser
info: System.Net.Http.HttpClient.SimpleKeycloakAuthServerSample.Server.ClientHandler[100]
      Sending HTTP request POST https://testapp.mkw.at/Account/CurrentUser
Response for /: StatusCode: 302
X-Original-Proto: http
X-Original-For: [::ffff:172.18.0.2]:37404
Request Path: /Account/Login
Request Method: GET
IsAuthenticated: True
AuthType: AuthenticationTypes.Federation
Redirecting to https://testapp.mkw.at/
Response for /Account/Login: StatusCode: 302
X-Original-Proto: http
X-Original-For: [::ffff:172.18.0.2]:37404
Request Path: /
Request Method: GET
IsAuthenticated: True
AuthType: AuthenticationTypes.Federation
info: System.Net.Http.HttpClient.SimpleKeycloakAuthServerSample.Server.LogicalHandler[100]
      Start processing HTTP request POST https://testapp.mkw.at/Account/CurrentUser
info: System.Net.Http.HttpClient.SimpleKeycloakAuthServerSample.Server.ClientHandler[100]
      Sending HTTP request POST https://testapp.mkw.at/Account/CurrentUser
Response for /: StatusCode: 302
X-Original-Proto: http

Configuration Details:
The Keycloak configuration should be okay, as i copied it out of the client adapter config.

My Authentication Setup in Program.cs:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddKeycloakWebApp(builder.Configuration.GetSection("Keycloak"), configureOpenIdConnectOptions: options => {
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    //options.ResponseType = OpenIdConnectResponseType.Code;
    //options.ResponseMode = OpenIdConnectResponseMode.FormPost;
    options.CallbackPath = "/signin-oidc";
    options.Scope.Add("openid");

    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = context =>
        {

            var identity = context.Principal?.Identity as ClaimsIdentity;
            if (identity != null && context.ProtocolMessage?.IdToken != null)
            {
                identity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
            }
            Console.WriteLine("Token validation successful! Tokens received:");
            return Task.CompletedTask;
        },
        OnRedirectToIdentityProvider = context =>
        {
            // Add checkLoginIframe parameter directly to the protocol message
            context.ProtocolMessage.SetParameter("checkLoginIframe", "false");
            return Task.CompletedTask;
        },

        OnRemoteFailure = context =>
        {
            Console.WriteLine($"Authentication failed: {context.Failure.Message}");
            context.Response.Redirect("/Account/Error");
            context.HandleResponse();
            return Task.CompletedTask;
        },
        OnSignedOutCallbackRedirect = context =>
        {
            context.Response.Redirect("/Account/Logout");
            context.HandleResponse();
            return Task.CompletedTask;
        },
        OnAuthenticationFailed = context =>
        {
            Console.WriteLine($"Authentication failed: {context.Exception.Message}");
            return Task.CompletedTask;
        },
        OnAuthorizationCodeReceived = context =>
        {
            Console.WriteLine("Authorization code received");
            return Task.CompletedTask;
        },
    };
});

Login Action in my AccountController:

        [HttpGet]
        public IActionResult Login(string redirectUri)
        {
            var redirectUrl = redirectUri ?? Url.Content("~/");
            Console.WriteLine($"Redirecting to {redirectUrl}");
            return Challenge(new AuthenticationProperties { RedirectUri = redirectUrl }, OpenIdConnectDefaults.AuthenticationScheme);
        }

CurrentUser Method in AccountController:

        [HttpPost]
        public ApplicationAuthenticationState CurrentUser()
        {
            return new ApplicationAuthenticationState
            {
                
                IsAuthenticated = User.Identity.IsAuthenticated,
                Name = User.Identity.Name,
                Claims = User.Claims.Select(c => new ApplicationClaim { Type = c.Type, Value = c.Value })
            };
        }

My Keycloak Client Access settings should be fine.

Backend docker-compose.yml:

version: '3.4'

services:
  simplekeycloakauthserversample.server:
    image: simplekeycloakauthserversampleserver
    build:
      context: .
      dockerfile: Server/Dockerfile
    networks:
      - itw4uBackendNet
    labels:
      # Traefik configurations for routing and authentication
      - "traefik.enable=true"
      - "traefik.http.routers.simplekeycloakauthserversample.rule=Host(`testapp.mkw.at`)"
      - "traefik.http.routers.simplekeycloakauthserversample.middlewares=simplekeycloakauthserversample_https-redirect,add-forwarded-headers@docker,add-forwarded-headers@file,secure-headers@file"
      
      # Middleware Definitions
      - "traefik.http.middlewares.simplekeycloakauthserversample_https-redirect.redirectscheme.scheme=https"  # Redirect to HTTPS
      - "traefik.http.middlewares.add-forwarded-headers@docker.headers.customRequestHeaders.X-Forwarded-Proto=https"  # Set forwarded headers
      - "traefik.http.middlewares.add-forwarded-headers@docker.headers.customRequestHeaders.X-Forwarded-For="  # Ensure this header is forwarded
      
      - "traefik.http.routers.simplekeycloakauthserversample.entrypoints=websecure"
      - "traefik.http.routers.simplekeycloakauthserversample.tls=true"
      - "traefik.http.routers.simplekeycloakauthserversample.service=simplekeycloakauthserversample"
      - "traefik.http.services.simplekeycloakauthserversample.loadbalancer.server.port=8080"
    volumes:
      - ./dataprotection-keys:/home/app/.aspnet/DataProtection-Keys
      - ~/.aspnet/https:/https:ro

networks:
  itw4uBackendNet:
    external: true

Troubleshooting So far:

  • Basically tried everything i found related to this topic
  • I've checked the Keycloak client configuration to ensure that the redirect URI matches exactly (https://testapp.mkw.at/signin-oidc).
  • The OpenID Connect middleware is configured in Program.cs, and the CallbackPath is correctly set to /signin-oidc.
  • Deleting cookies as some people suggested didn’t solve the issue.
  • There are no specific errors in the application logs, but the browser just keeps redirecting
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseForwardedHeaders();
app.UseHeaderPropagation();
app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.UseEndpoints(endpoints =>
{
    app.MapControllers();
    app.MapRazorComponents<App>()
        .AddInteractiveWebAssemblyRenderMode()
        .AddAdditionalAssemblies(typeof(SimpleKeycloakAuthServerSample.Client._Imports).Assembly);
    app.MapRazorPages();
});
app.Run();

tried every order possible here but also did nothing.

Question:

  1. What could be causing this redirect loop between Keycloak and the Blazor application?
  2. Is there any additional configuration I might be missing, either on Keycloak or in the Blazor app, that could prevent this loop?
  3. Or is there just a simple misconfiguration that i oversaw because of looking to deep in the problem?
  4. Could this be a Problem related to the Keycloak Server, because its not running in the same docker environment

Could this be related to how Radzen components interact with the authentication flow?

When someone wants to look deeper into this problem and wants to help me, I am happy to provide more information (traefik docker-compose.yml file for example)

Any help or pointers would be greatly appreciated!

Thanks in advance!

Enable and check Traefik debug log (doc) and Traefik access log in JSON format (doc).

Also check your browser's developer tools' network tab.

Personally I would place repetitive things globally on entrypoint, like http-to-https redirect. Also X-Forward headers are usually added automatically by Traefik. Compare to simple Traefik example.