openid connect - ASP.NET Core 9 MVC app with conditional redirect to OIDC provider - Stack Overflow

admin2025-04-25  3

I have an ASP.NET Core 9 MVC app that I have recently migrated to .NET 9. Previously it was on ASP.NET MVC. The app have two authentication mechanisms a login page and an OIDC provider to which the app redirects when I enable the bit in app settings.

I have tried to migrate what I had previously in the startup.cs to the new program.cs minimal hosting model. The issue is previously when I set the use OIDC bit to false the login page opens up but If I do the same in the migrated app the app still gets redirected to the OIDC authority and then opens up the Login page. I need help in finding what am I doing wrong with this.

This is my Index method of HomeController:

 [Authorize]
 public IActionResult Index(string id)
 {
     try
     {
         bool redirectToThirdParty= _configuration["redirectToThirdParty"] != null ? Convert.ToBoolean(_configuration["redirectToThirdParty"]) : false;

         if (redirectToThirdParty)
         {
             string thirdPartyURL= _configuration["thirdPartyURL"];
             if (!string.IsNullOrEmpty(thirdPartyURL))
             {
                 return Redirect(thirdPartyURL);
             }
         }

         HttpContext.Session.SetString("isAuthorized", "False");
         HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Accepted;
     }
     catch (Exception ex)
     {
         _logger.LogError(ex, "Index " + ex.Message, null);
     }

     bool oidcEnabled = Convert.ToBoolean(_configuration["oidcEnabled"]);

     if (!oidcEnabled)
     {
         return View("Login");
     }
     else
     {
         return View();
     }
 }

And here is my program.cs:

var builder = WebApplication.CreateBuilder(args);
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
builder.Services.AddControllersWithViews().AddJsonOptions(options => {
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddKendo();
builder.Services.AddScoped<VersionedContent>();
builder.Services.AddSingleton<PropertiesMiddleware>();
builder.Services.AddSingleton<ConcurrentDictionary<string, IFormCollection>>();
builder.Services.AddHttpContextAccessor();
ConcurrentDictionary<string, IFormCollection> userFormValues = new ConcurrentDictionary<string, IFormCollection>(StringComparer.InvariantCultureIgnoreCase);
builder.Services.AddSession();
builder.Host.ConfigureLogging(logging=>
{
    logging.ClearProviders();
    logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
    logging.AddConsole();
}).UseNLog();
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
    builder =>
    {
        builder
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
    });
});
builder.Services.AddSignalR();
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { NameClaimType = "name", RoleClaimType = "role" };
        options.Authority = builder.Configuration.GetValue<string>("OIDCAuthority");
        options.ClientId = builder.Configuration.GetValue<string>("OIDCClient");
        options.CallbackPath = "/Home/Index";
        options.SignedOutRedirectUri = "/Home/Index";
        options.ResponseType = "id_token";
        options.Scope.Add(builder.Configuration.GetValue<string>("OIDCScope"));
        options.SkipUnrecognizedRequests = true;
        options.Events = new OpenIdConnectEvents
        {
            OnTokenValidated = (context) => {
                var identity = context.Principal.Identity as ClaimsIdentity;
                if (identity != null)
                {
                    var id = identity.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
                    var roles = identity.FindAll(c => c.Type == "roles");
                    var name = identity.Claims.FirstOrDefault(c => c.Type == "name")?.Value;
                    var db = identity.Claims.FirstOrDefault(c => c.Type == "db")?.Value;
                    var id = string.IsNullOrEmpty(id) ? identity.Claims.FirstOrDefault(c => c.Type == "id")?.Value : id;
                    if (string.IsNullOrEmpty(id))
                    {
                        throw new Exception("ID does not exist.");
                    }
                    string assignedRoles = roles.FirstOrDefault()?.Value;
                    if (roles != null)
                    {
                        foreach (var r in roles.ToList())
                        {
                            identity.AddClaim(new Claim(identity.RoleClaimType, r.Value.Split('-')[1]));
                            assignedRoles += "," + r.Value;
                        }
                    }
                    identity.AddClaim(new Claim(identity.NameClaimType, name + " " + db));
                    identity.AddClaim(new Claim("name", name));
                    identity.AddClaim(new Claim("db", db));
                    identity.AddClaim(new Claim("id", id));
                    identity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
                }
                return Task.CompletedTask;
            },
            OnRedirectToIdentityProvider = (context) => {
                if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                {
                    var idTokenHint = context.HttpContext.User.FindFirst("id_token");
                    if (idTokenHint != null)
                    {
                        context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                    }
                    else if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                    {
                        var path = context.HttpContext.Request.Path.Value.ToLower();
                        if (path == "/home/app" || path == "/offline/app")
                        {
                            try
                            {
                                logger.Log(NLog.LogLevel.Info, "RedirectToIdentityProvider User redirected from Launcher.");
                                var formData = context.HttpContext.Request.ReadFormAsync();
                                formData.Wait();

                                if (formData.Result != null)
                                {
                                    var results = formData.Result;
                                    if (formData.Result.TryGetValue("sessionKey", out var sessionKey))
                                    {
                                        logger.Log(NLog.LogLevel.Info, "RedirectToIdentityProvider." + string.Format("Agent: {0}, redirected from Launcher.", sessionKey));
                                        IFormCollection temp;
                                        userFormValues.TryRemove(sessionKey, out temp);
                                        userFormValues.TryAdd(sessionKey, formData.Result);
                                    }
                                    else
                                    {
                                        logger.Log(NLog.LogLevel.Error, "RedirectToIdentityProvider User redirected from Launcher with no sessionKey.");
                                    }
                                }
                                else
                                {
                                    logger.Log(NLog.LogLevel.Error, "RedirectToIdentityProvider User redirected from Launcher with incorrect form values.");
                                }
                            }
                            catch (Exception ex)
                            {
                                logger.Log(NLog.LogLevel.Error, ex, ex.Message);
                            }
                        }
                    }
                }
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = (context) =>
            {
                logger.Log(NLog.LogLevel.Error, "Authenticatoin Failed" + context.Exception, context.Exception.Message);
                return Task.CompletedTask;
            }
        };
    });

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMiddleware<PropertiesMiddleware>();
app.UseRouting();
app.UseCors(o => o.AllowAnyHeader().AllowAnyHeader().AllowAnyOrigin());
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
logger.Debug("init main");
app.Run();

I have an ASP.NET Core 9 MVC app that I have recently migrated to .NET 9. Previously it was on ASP.NET MVC. The app have two authentication mechanisms a login page and an OIDC provider to which the app redirects when I enable the bit in app settings.

I have tried to migrate what I had previously in the startup.cs to the new program.cs minimal hosting model. The issue is previously when I set the use OIDC bit to false the login page opens up but If I do the same in the migrated app the app still gets redirected to the OIDC authority and then opens up the Login page. I need help in finding what am I doing wrong with this.

This is my Index method of HomeController:

 [Authorize]
 public IActionResult Index(string id)
 {
     try
     {
         bool redirectToThirdParty= _configuration["redirectToThirdParty"] != null ? Convert.ToBoolean(_configuration["redirectToThirdParty"]) : false;

         if (redirectToThirdParty)
         {
             string thirdPartyURL= _configuration["thirdPartyURL"];
             if (!string.IsNullOrEmpty(thirdPartyURL))
             {
                 return Redirect(thirdPartyURL);
             }
         }

         HttpContext.Session.SetString("isAuthorized", "False");
         HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Accepted;
     }
     catch (Exception ex)
     {
         _logger.LogError(ex, "Index " + ex.Message, null);
     }

     bool oidcEnabled = Convert.ToBoolean(_configuration["oidcEnabled"]);

     if (!oidcEnabled)
     {
         return View("Login");
     }
     else
     {
         return View();
     }
 }

And here is my program.cs:

var builder = WebApplication.CreateBuilder(args);
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
builder.Services.AddControllersWithViews().AddJsonOptions(options => {
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddKendo();
builder.Services.AddScoped<VersionedContent>();
builder.Services.AddSingleton<PropertiesMiddleware>();
builder.Services.AddSingleton<ConcurrentDictionary<string, IFormCollection>>();
builder.Services.AddHttpContextAccessor();
ConcurrentDictionary<string, IFormCollection> userFormValues = new ConcurrentDictionary<string, IFormCollection>(StringComparer.InvariantCultureIgnoreCase);
builder.Services.AddSession();
builder.Host.ConfigureLogging(logging=>
{
    logging.ClearProviders();
    logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
    logging.AddConsole();
}).UseNLog();
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
    builder =>
    {
        builder
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader();
    });
});
builder.Services.AddSignalR();
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() { NameClaimType = "name", RoleClaimType = "role" };
        options.Authority = builder.Configuration.GetValue<string>("OIDCAuthority");
        options.ClientId = builder.Configuration.GetValue<string>("OIDCClient");
        options.CallbackPath = "/Home/Index";
        options.SignedOutRedirectUri = "/Home/Index";
        options.ResponseType = "id_token";
        options.Scope.Add(builder.Configuration.GetValue<string>("OIDCScope"));
        options.SkipUnrecognizedRequests = true;
        options.Events = new OpenIdConnectEvents
        {
            OnTokenValidated = (context) => {
                var identity = context.Principal.Identity as ClaimsIdentity;
                if (identity != null)
                {
                    var id = identity.Claims.FirstOrDefault(c => c.Type == "id")?.Value;
                    var roles = identity.FindAll(c => c.Type == "roles");
                    var name = identity.Claims.FirstOrDefault(c => c.Type == "name")?.Value;
                    var db = identity.Claims.FirstOrDefault(c => c.Type == "db")?.Value;
                    var id = string.IsNullOrEmpty(id) ? identity.Claims.FirstOrDefault(c => c.Type == "id")?.Value : id;
                    if (string.IsNullOrEmpty(id))
                    {
                        throw new Exception("ID does not exist.");
                    }
                    string assignedRoles = roles.FirstOrDefault()?.Value;
                    if (roles != null)
                    {
                        foreach (var r in roles.ToList())
                        {
                            identity.AddClaim(new Claim(identity.RoleClaimType, r.Value.Split('-')[1]));
                            assignedRoles += "," + r.Value;
                        }
                    }
                    identity.AddClaim(new Claim(identity.NameClaimType, name + " " + db));
                    identity.AddClaim(new Claim("name", name));
                    identity.AddClaim(new Claim("db", db));
                    identity.AddClaim(new Claim("id", id));
                    identity.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
                }
                return Task.CompletedTask;
            },
            OnRedirectToIdentityProvider = (context) => {
                if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                {
                    var idTokenHint = context.HttpContext.User.FindFirst("id_token");
                    if (idTokenHint != null)
                    {
                        context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                    }
                    else if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                    {
                        var path = context.HttpContext.Request.Path.Value.ToLower();
                        if (path == "/home/app" || path == "/offline/app")
                        {
                            try
                            {
                                logger.Log(NLog.LogLevel.Info, "RedirectToIdentityProvider User redirected from Launcher.");
                                var formData = context.HttpContext.Request.ReadFormAsync();
                                formData.Wait();

                                if (formData.Result != null)
                                {
                                    var results = formData.Result;
                                    if (formData.Result.TryGetValue("sessionKey", out var sessionKey))
                                    {
                                        logger.Log(NLog.LogLevel.Info, "RedirectToIdentityProvider." + string.Format("Agent: {0}, redirected from Launcher.", sessionKey));
                                        IFormCollection temp;
                                        userFormValues.TryRemove(sessionKey, out temp);
                                        userFormValues.TryAdd(sessionKey, formData.Result);
                                    }
                                    else
                                    {
                                        logger.Log(NLog.LogLevel.Error, "RedirectToIdentityProvider User redirected from Launcher with no sessionKey.");
                                    }
                                }
                                else
                                {
                                    logger.Log(NLog.LogLevel.Error, "RedirectToIdentityProvider User redirected from Launcher with incorrect form values.");
                                }
                            }
                            catch (Exception ex)
                            {
                                logger.Log(NLog.LogLevel.Error, ex, ex.Message);
                            }
                        }
                    }
                }
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = (context) =>
            {
                logger.Log(NLog.LogLevel.Error, "Authenticatoin Failed" + context.Exception, context.Exception.Message);
                return Task.CompletedTask;
            }
        };
    });

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMiddleware<PropertiesMiddleware>();
app.UseRouting();
app.UseCors(o => o.AllowAnyHeader().AllowAnyHeader().AllowAnyOrigin());
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
logger.Debug("init main");
app.Run();
Share Improve this question edited Jan 16 at 14:45 marc_s 757k184 gold badges1.4k silver badges1.5k bronze badges asked Jan 16 at 13:57 IntelligentCancerIntelligentCancer 1492 gold badges3 silver badges19 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

The issue might relate the new feature in ASP.NET Core 9: OpenIdConnectHandler adds support for Pushed Authorization Requests (PAR)

The PAR works as below:

  • The client application sends a POST request to the authorization server with the authorization request parameters
  • The authorization server responds with an identifier for the request
  • The client application redirects the user's browser to the authorization server with the identifier

For .NET 9, they have decided to enable PAR by default if the identity provider's discovery document advertises support for PAR, since it should provide enhanced security for providers that support it. The identity provider's discovery document is usually found at .well-known/openid-configuration. If this causes problems, you can disable PAR via OpenIdConnectOptions.PushedAuthorizationBehavior as follows:

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", oidcOptions =>
    {
        // Other provider-specific configuration goes here.

        // The default value is PushedAuthorizationBehavior.UseIfAvailable.

        // 'OpenIdConnectOptions' does not contain a definition for 'PushedAuthorizationBehavior'
        // and no accessible extension method 'PushedAuthorizationBehavior' accepting a first argument
        // of type 'OpenIdConnectOptions' could be found
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });
转载请注明原文地址:http://anycun.com/QandA/1745527971a90791.html