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

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.

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

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
})

Azure Service App [Mobileservice] - Custom error response

I've been working on an app and sometimes during my coding I receive 500 Internal Server Error with no content, which isn't really what it's suppose to show. I wrote a code to test the error response in the controller as following:
[HttpGet]
[Route("TryCatch")]
public IHttpActionResult TestCatch()
{
try
{
object obj = null;
int i = (int) obj;
return Ok();
}
catch (Exception e)
{
return InternalServerError(e);
}
}
When I try this locally, I do receive the information regarding the error that I wished for. As for in production, all I see is (in postman) {
"message": "An error has occurred."
} and the status code.
I've looked around and found what I thought would be solution to my problem: Internal Server Error - Azure App Service Custom Controller but it wasn't.
And adding <httpErrors errorMode="Custom" defaultResponseMode="passThrough"> or <customErrors mode="On"/> or anything in web.config doesn't help either.
Any help is appreciated!
Please change the custom errors mode to Off instead of On.
<customErrors mode="Off"/>
And change IncludeErrorDetailPolicy property to Always in WebApiConfig.cs file(Azure Mobile Service) or Startup.cs file(Azure Mobile App).
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
After that, I can get the detail exception message from the exceptionMessage property.
{"message":"An error has occurred.","exceptionMessage":"Object reference not set to an instance of an object.","exceptionType":"System.NullReferenceException","stackTrace":""}

requets are redirected to login.aspx?ReturnUrl=

I have implemented a webservice using servicestack using Visual Studio. Running the service from the vs debugger works just fine. I have just tried to deploy it to a debian machine using XSP4. The service makes use of logging, from what I can tell the service is up and running. A log file is created when I start the service, but any request I make does not work. For instance, I make the following request using a browser:
http://127.0.0.1/Activity/5b1e5316-8ea5-4ba5-aaee-7f40151b80d3/Unit
But the browser is being redirected to:
http://127.0.0.1/login.aspx?ReturnUrl=%2fActivity%2f5b1e5316-8ea5-4ba5-aaee-7f40151b80d3%2fUnit
I have implemented my own authentication using a global requestfilter that I add in the Configure method. I am very confused why the request is redirected to login.aspx. Also, in the log file is see the following:
Error 2013-01-10 00:07:53.2631 NotFoundHttpHandler 192.168.23.2 Request not found: /login.aspx?ReturnUrl=%2fActivity%2f5b1e5316-8ea5-4ba5-aaee-7f40151b80d3%2fUnit
Does anybody have any idea what may cause this behaviour? Here is the code that adds the global filter:
this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
try
{
var userCreds = httpReq.GetBasicAuthUserAndPassword();
if (userCreds == null)
{
httpResp.ReturnAuthRequired();
return;
}
var userName = userCreds.Value.Key;
var userPass = userCreds.Value.Value;
if (!TryResolve<IAuthenticationProvider>().AuthenticateUser(userName, userPass))
{
httpResp.ReturnAuthRequired();
}
return;
}
catch (Exception ex)
{
log.Error(ex);
throw new ApplicationException(ex.Message, ex);
}
});
I just figured it out. I added
<authentication mode="None" />
to the Web.config like so:
<system.web>
<!-- mode=[Windows|Forms|Passport|None] -->
<authentication mode="Windows" />
</system.web>
The documentation can be found here: msdn.microsoft.com/en-us/library/aa291347(v=vs.71).aspx
I used the snippet below to solve my woes with .NET Forms auth overriding the 401 I'm trying to return via ServiceStack.NET. On every request, at the beginning, if it's AJAX, immediately suppress the behavior. This flag was created by MS for just this purpose. http://msdn.microsoft.com/en-us/library/system.web.httpresponse.suppressformsauthenticationredirect.aspx
Note that many other solutions that show up when searching tell you disable .NET auth, which is simply not feasible in the majority of cases. This code works with no other modifications.
protected void Application_BeginRequest()
{
HttpRequestBase request = new HttpRequestWrapper(Context.Request);
if (request.IsAjaxRequest())
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}

Resources