Too many redirects MVC project - owin

I have a .net project using Azure Active Directory as the SSO. Tested and everything works fine locally.
I deployed the project on an environment with 2 load balancer and I started getting error message :
This page isn’t working
xxx.com redirected you too many times.
Try clearing your cookies.
ERR_TOO_MANY_REDIRECTS
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
//This is the line causing the issue
GlobalFilters.Filters.Add(new RequireHttpsAttribute());
}
In chrome at time the site is loaded properly but in IE, there are too many cookies that are being created for OpenId.
I tried this also:
protected void Application_BeginRequest()
{
if (!Context.Request.IsSecureConnection)
Response.Redirect(Context.Request.Url.ToString().Replace("http:",
"https:"));
}
I even tried the KentorOwin:
app.SetDefaultSignInAsAuthenticationType
(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions());
But nothing seems working.
Any help please?
Thanks.

I came to know that the environment I am using is accepting HTTPS as request which then is redirected to HTTP as there are load balancers.
Might be the cause of the probs.

Related

Azure AD : how to redirect to sign in page by code manually

I followed this official sample to integrate Azure AD authentication into my ASP.NET Core application. Everything is working well, and I can call the MS Graph API successfully.
The problem is that after I signed in, I kept the browser opening and then I shutdown my application (simulate the server crashed), then I run the application again, then refreshing the page, I got an exception as shown in this screenshot:
I debugged the code, and I found that the request went into my controller (I had [Authorize] on my controller so it should redirect to sign in page when app found the request doesn't be authenticated) and the exception appeared when run
var me = await _graphClient.Me.Request().GetAsync();
This means the app thinks the request is authenticated.
[Authorize]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphClient;
public HomeController(GraphServiceClient graphClient, ILogger<HomeController> logger)
{
_logger = logger;
_graphClient = graphClient;
}
public async Task<IActionResult> IndexAsync()
{
var me = await _graphClient.Me.Request().GetAsync();
ViewBag.Myname = me.DisplayName;
return View();
}
}
The reason for why the app not redirected to sign in page is that the browser stored cookie, after removing the cookie manually, the exception wouldn't appear and app would redirect to sign in page.
I think I need a global filter to handle the exception, by removing the cookie (I'm not sure if it can remove the cookie) and then redirect to sign in page manually, but I failed to find any document to describe how to redirect to sign in page manually by code.
Any document or code sample is appreciated.
By the way, should we put effort on this kind of scenario?
• As per my understanding on your query regarding the redirection to sign in page manually through code, I would suggest you to please go through the below code which suggests the same mechanism as you are expecting, i.e., delete the cookie from browser memory for the session signed in and redirect to the sign in page again for login to a new session, but the below code is for ‘laravel’: -
public function logout(Request $request)
{
$cookie = \Cookie::forget('first_time');
$this->guard()->logout();
$request->session()->invalidate();
return $this->loggedOut($request) ?: redirect('/')-
>withCookie($cookie);
Also, you can use ‘\Cookie::queue(\Cookie::forget('first_time'));’ to avoid creating the cookie variable and redirecting with it.
Kindly find the link below for more details: -
https://laracasts.com/discuss/channels/laravel/cookie-is-not-deleting-using-cookieforget-after-logout
• Similarly, I would suggest you follow the cookie-based identity authentication method using the ASP .Net Core sample code as below: -
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication
(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/";
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapDefaultControllerRoute();
app.Run();
For more information, kindly find the documentation link below which explains the cookie authentication configuration in detail: -
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0

Azure portal. Mvc. Redirect to custom error page doesn't work for 403 error

I have configured owin role-based authorization in my MVC application.
Then, I need to add a custom handling of 403 http error
I know two approaches to do it:
Web.config settings:
<customErrors mode="Off" defaultRedirect="~/Error/" redirectMode="ResponseRedirect">
<error statusCode="403" redirect="~/Error/NoAccess" />
</customErrors>
Configuration inside of overridden HandleUnauthorizedRequest method in Authorize attribute:
if (filterContext.HttpContext.User?.Identity.IsAuthenticated ?? false)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{"action", "NoAccess"},
{"controller", "Error"}
});
//I've tried two variants of below: with below line as well as without it
filterContext.Result.ExecuteResult(filterContext.Controller.ControllerContext);
}
Both of these methods work well in my local machine when I try to get access to resources which is not allowed for my user and I see my custom page for 403 error, but when I deploy my application on azure portal, I see only white page with the following text : 'You do not have permission to view this directory or page.'. As I may understand, I need to configure this behavior on azure portal as well as I configured it in my code.
Could someone advise with it?
I've found the answer.
The problem is appeared in case if I set response code in controller method manually as shown below:
public ActionResult NoAccess()
{
Response.StatusCode = 403;
return View();
}
In case if I delete this status setup, redirection works fine.
The solution is to set to true the following flag : TrySkipIisCustomErrors
public ActionResult NoAccess()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 403;
return View();
}
Then everything works correctly as well.

Suppress affinity cookie to force client to another Azure app node

I have a C# web application that uses a component (Progress Telerik Sitefinity CMS) that takes a long time (2 minutes) to initialize. A user who visits the site while in this stage, will be redirected to a page that polls the state every second, until initialization is complete. (This is built-in Sitefinity behavior).
I'm hosting my application within an Azure App Service. If I increase the number of instances (scale up), some of my users end up on the new node while it's still initializing. Problem is, due to the affinity cookie Azure adds, they stay on this node.
I want affinity, except when the site is initializing. In that case, I want to drop the cookie and poll. In that case I get assigned a random node, so an initialzed node is found within seconds.
Question is: how do I achieve this? Much of what happens is handled within Sitefinity, so I resorted to altering content in my global.asax. It doesn't work. I tried to put this in my global.asax.cs:
protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
var path = HttpContext.Current.Request.Url.AbsolutePath;
// "/sitefinity/status" is the page the client is redirected to
// "/appstatus" is used to poll initialization status
if (path == "/appstatus" || path == "/sitefinity/status")
{
// "ARRAffinity" is the Azure affinity cookie
Response.Cookies.Remove("ARRAffinity");
// just removing the cookie didn't work so i tried to override it
Response.Cookies.Add(new HttpCookie("ARRAffinity", "-") { HttpOnly = true });
// reportedly, this suppresses cookie adding by Azure
Response.Headers.Add("ARR-Disable-Session-Affinity", "true");
};
}
How do I force my client to a different node?
EDIT
I think I found (part of) the problem here.
First, "/" is requested. This returns a 302 redirect, but also the ARRAffinity cookie.
Then, "/sitefinity/status" is requested. Both the ARR-Disable-Session-Affinity and the cookie are stripped. This means, the cookie is not cleared on the client.
While polling, the client already has the cookie. So the user is never redirected to another node.
So that's might be the problem. Now to solve it...
EDIT
I followed Vesselin Vassilevs suggestion and added this to my sites configuration file:
<appSettings>
<add key="sf:AppStatusPageResponseCode" value="503" />
</appSettings>
But because I still incidentally reach the initializing node, I also suppressed the affinity cookie , by altering my global.asax.cs:
protected void Application_EndRequest(object sender, EventArgs e)
{
var httpCode = Response.StatusCode;
var isRedirectBackFromStatusPage = httpCode == 302 && Request.Url.AbsolutePath == "/sitefinity/status";
var isFinalSitefinityStatusPoll = httpCode == 404 && Request.Url.AbsolutePath == "/appstatus";
if (isRedirectBackFromStatusPage || isFinalSitefinityStatusPoll)
{
var cookie = Request.Cookies["ARRAffinity"];
if (cookie != null) Response.Cookies.Add(cookie);
return;
}
if (httpCode != 200 || !Response.ContentType.StartsWith("text/html"))
{
Response.Headers.Add("ARR-Disable-Session-Affinity", "true");
};
}
Why not disable the arr affinity cookie altogether?
Sitefinity backend works fine with no arr cookie and with multiple instances.
EDIT: We need to tell Azure that the site is not yet ready during the Sitefinity initialization. The problem with that is, that the appStatus page (shown by Sitefinity during init) returns status code 302 and even 200, which makes Azure to believe the site is running ok. I've wrote about this here: https://sitefinitydevelopment.com/blog/sitefinity's-application-status-page-can-cause-you-big-problems.html
Depending on your Sitefinity version, you can either implement the custom solution there (manually returning http code 503 during system restart) or set the following setting in the web.config (Sitefinity 9+)
<add key="sf:AppStatusPageResponseCode" value="503" />

Infinite re-direct loop after AAD Authentication when redirect is specified

If I specify a redirect URI in my OpenIdConnectAuthenticationOptions like so
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
Then I get an infinite re-direct loop. This only happens though when i put it on and standalone IIS Server (our test server). If i remove all the Replay url's in AAD and leave it only setup for the test server, and remove the "RedirectUri = redirectUri," from the above my problem goes away.
I have a fiddler log here : https://drive.google.com/file/d/0B5Ap95E_wdyAa0RLLWloZ0dCaGM/view?usp=sharing
It appears that when request from AAD comes back to my app, before the token is grabbed and used, the Middle Ware is just bouncing it right back with a 302. Also what may be important, I have the [Authorize] attribute over the mvc controller that the routing and return uri directs to. If I remove it i do not get this issue.
[UPDATE]
I tried moving the application to my localhost install of IIS rather than using iisexpress so that i could setup as a SubApplication like it is on my iis server. On my localhost it does the same infinite loop. I added in some telemetry custom events on an override of the [Authorize] attribute and have been able to discover that when page is re-directed back to the application after authentication httpContext.user.identity.IsAuthenticated = false. So somehow the OWIN middle ware is not setting this to true?
Thanks for any help!
I was able to find a solution to my problem. Originally i was specifying my reply url's to point to the root of the site. My rout config looks like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Welcome", action = "Index", id = UrlParameter.Optional }
);
If I append "Welcome" to the end of my reply url it works. For some reason if i leave the reply url to the root of the site and have the default route picked up it just goes into an infinite loop.
I found out also that this only applies to a sub application of a site. I tried moving my application to be a standalone site in iis so rather than and I didnt have to add the controller name in the reply url.
Example:
Original reply url:
mysite.mydomain.com/CustomApp
New Reply url:
mysite.mydomain.com/CustomApp/Welcome
Hope someone else can find this useful!
UPDATE
I found out that the root of the problem was still caused from this mvc5 bug: katanaproject.codeplex.com/workitem/197. I thought it had been fixed but it has not, so i will continue to use the well known Kentor Owin Cookie Saver: github.com/Sustainsys/owin-cookie-saver
Solved by using Never option for CookieSecureOption
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieSecure = CookieSecureOption.Never
})

Must close browser to get authentication data in PostConstruct method

I am trying to cache the authentication information in a SessionScoped managed bean.
When I open a browser and login into the server (the browser asks me for username/password) the first time, it works as it should.
The trouble comes when I restart the webapp or the server (it is a development setup). Then, accessing the webapp from one of the browser where I had previously logged in, causes that in my #PostConstruct method, FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal() returns null (I check that it is effectively executed).
On the other hand, if I just check that value from getUser(), it works correctly.
AFAIK, I expected the browser the just cache the credentials and that when I was reentering the application after a restart the only difference was that the browser would automatically send the credentials without prompting me again. I did not expect that it would make any difference in the server.
The code is the following (simplified)
#ManagedBean
#SessionScoped
public class UserManager {
private Principal userPrincipal = null;
#PostConstruct
public void init() {
this.userPrincipal =
FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
System.out.println("EN POSTCONSTRUCT DE LDAP PRINCIPAL!! " + this.userPrincipal);
}
public String getUser() {
return this.userPrincipal.getName();
}
}
The setup is JBoss 6.1 Final with Mojarra 2.03, JDK 6. I have tested it both with IE7 and Firefox.
UPDATE: I have found more about it. If I go into my welcome page, then the webapp works as expected. It is when I try to directly access another page (*1) that it fails to initialize the user info.
*1: I am not talking about reloading the page as this always fails, but typing http://myserver/mywebapp/page_that_is_not_welcome_one.xhtml in the URL bar.

Resources