Returning Descriptive 401 Messages From Owin WebApi2 - asp.net

I have an asp.net 4.5 web api running using owin. Whenever an unauthorized request is made it returns a 401 with the following response as expected:
{"Message":"Authorization has been denied for this request."}
I would like to add additional detail to this response (expired token, invalid role, etc...) and implemented a custom [AuthorizeAttribute] based on this SO post.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var response = actionContext.Request.CreateResponse<MyError>
(new MyError() { Description = "This is why you're unauthorized" });
response.StatusCode = HttpStatusCode.Unauthorized;
actionContext.Response = response;
}
}
and then used it on my controllers like this:
[MyAuthorizeAttribute(Roles = "Foo")]
public class MyController : ApiController
{
...
}
which returns a 401 with the following response as expected:
{"Description": "This is why you're unauthorized"}
However, I do not see how to determine the reason the request is unauthorized from the HttpActionContext passed to MyAuthorizeAttribute.HandleUnauthorizedRequest. For instance, when I'm debugging locally and make a request with an expired token it throws a SecurityTokenExpiredException explaining IDX10223: Lifetime validation failed. The token is expired. ValidTo: '...' Current time: '...'. or with a invalid audience it throws a SecurityTokenInvalidAudienceException explaining Message=IDX10214: Audience validation failed. Audiences: '...'. Did not match: validationParameters.ValidAudience: 'null' or validationParameters.ValidAudiences: '...'. I've set several breakpoints in my Startup.cs yet have been unable to even catch one of these exceptions before they're thrown.
How can I determine the specific reason a request is unauthorized using owin middleware?

Haven't figured out how to identify expirations nor invalid audiences and such, but I ended up using this to at least return 403s based on roles.
You can customize the message ("You must have role X to access this action ...") using the example in my question above.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
base.HandleUnauthorizedRequest(actionContext);
if (actionContext.RequestContext.Principal != null &&
actionContext.RequestContext.Principal.Identity.IsAuthenticated &&
Roles != null)
{
if (!Roles.Split(',').Any(x =>
actionContext.RequestContext.Principal.IsInRole(x.Trim())))
{
actionContext.Response.StatusCode = HttpStatusCode.Forbidden;
}
}
}
}

Related

Spring SAML 2.0 with Dynamic relying registration, doesn't work on Reverse Proxy Nginx

I've been looking for a solution in many questions and forums but nothing worked for me.
I've a use case, in which we register Idp configuration in another service, and then we retrieve that particular configuration providing an orgId query param. Somehow I could built something that works on localhost with Okta as Idp, based on these sites:
Here I could delegate Idp selection before real saml auth gets triggered:
Spring Security SAML2 dynamic selection of IDPs or dynamic URLs for them
It works on localhost even SLO, but when we deploy the service to a EKR cluster, for all backend services, at first we can load the login redirection like:
http://dev.org.com/services/sso/auth-sso?orgId=<id>
It goes to Okta, and we can login, when it tries to redirect back to our service, the "/" path doesn't get triggered and somehow the url it's redirected to
http://dev.org.com/auth-sso?error
We have tried, context-path setting in application.yml with no success, and lastly I read this reference
https://docs.spring.io/spring-security-saml/docs/1.0.4.BUILD-SNAPSHOT/reference/html/configuration-advanced.html#configuration-load-balancing
Also referenced to this questions:
Spring SAML 2.0 behind Nginx
but this spring saml version, doesn't recognize SAMLContextProviderLB or SAMLContextProviderImpl
Here some code:
build.gradle.kt:
constraints {
implementation("org.opensaml:opensaml-core:4.1.1")
implementation("org.opensaml:opensaml-saml-api:4.1.1")
implementation("org.opensaml:opensaml-saml-impl:4.1.1")
}
// spring
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
// saml2
implementation("org.springframework.security.extensions:spring-security-saml2-core:2.0.0.M31")
implementation("org.springframework.security:spring-security-saml2-service-provider:5.7.4")
In my SamlConfig class, I define these beans:
securityFilterChain:
#Bean
fun securityWebFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.authorizeRequests {
it
.mvcMatchers("/auth-sso").permitAll()
.mvcMatchers("/actuator/*").permitAll()
.anyRequest().authenticated()
}
.saml2Login {
it.loginPage("/auth-sso")
}
.saml2Logout{}
return http.build()
}
and a custom implementation of RelyingPartyRegistrationRepository, as LazyRelyingPartyRegistrationRepository
:
#Bean
protected fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
return LazyRelyingPartyRegistrationRepository()
}
And this is my /auth-sso
#GetMapping(value = ["/auth-sso"])
#ResponseBody
fun login(
#RequestParam(name = "orgId", required = true) orgId: String,
request: HttpServletRequest,
response: HttpServletResponse
) {
try {
val id = runBlocking {
findIdpConfigurationUseCase.execute(object : UserOrganizationLogin {
override val organizationId = organizationId
override val token: String? = null
})
}
val spInitiateUrl = "saml2/authenticate/$id"
log.debug("Redirecting to {}", spInitiateUrl)
response.sendRedirect(spInitiateUrl)
}
catch (e: Exception) {
log.error("Error preparing assertion info: {}", e)
response.sendRedirect(MessageFormat.format(environment.getRequiredProperty(HOME_URL), ""))
}
}
My "auth entry point" / which recieves the Principal crendentials and login the user calling a use case with internally create user and generates token:
#RequestMapping("/")
fun index(model: Model, #AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, response: HttpServletResponse) {
log.debug("User is authenticated!!!")
val loginInfo = runBlocking {
loginUserUseCase.execute(principal)
}
response.sendRedirect(
environment.getRequiredProperty(HOME_URL) +
MessageFormat.format(environment.getRequiredProperty(SSO_HOME_PARAMS), loginInfo.token, loginInfo.organizationId)
)
}

How to handle exceptions in Odata V4 client?

Asp.Net Web API Odata Controller Action:
public async Task<IHttpActionResult> Post(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Created(product);
}
Odata client code:
(Odata v4 client code generator v4)
static void AddProduct(Default.Container container, ProductService.Models.Product product)
{
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine("Response: {0}", operationResponse.StatusCode);
}
}
I would like to handle exception in a proper way inside AddProducts() Method while saving the changes.
How can I catch process the ModelState error which is sent from server return BadRequest(ModelState);?
Finally I just want to show the error message to the end uses which was sent from server.
Example:
"Product category is required."
What is the use of ODataException class? Will this help me?
Please help me.
if I understood well, you want to intercept that the ModelState is not valid, and customize the OData error that is shown to the user.
If you just want that the errors of the invalid model show up in the returned payload, you can use:
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
If you want to fully control the exceptions handling and messages shown, I'd suggest several action points for you to accomplish this:
Intercept ModelState is not valid: you can do this with a custom ActionFilterAttribute. In there, you can override the method OnActionExecuting(HttpActionContext actionContext). You can access the ModelState through actionContext.ModelState, check if it is valid, check the fields that have errors, check the nature of these errors and the generated messages for these errors, etc. The ModelState may be not valid for different reasons, like different types than the expected, not meet requirements specified by DataAnnotations, etc. You can check more on Model validation in here. For your case, I guess the Product entity will have a Required data annotation in the Category field.
After checking all errors, you can throw a custom Exception with the error/list of errors with the messages you want. This is necessary to later intercept your custom exception and be able to return your custom message in the error payload.
Intercept your custom exception: create a custom ExceptionFilterAttribute to intercept your thrown exceptions. Overriding the
OnException(HttpActionExecutedContext filterContext) you will have access to the exception, and inspecting it you will be able to build your proper OdataError:
In here you should return the HttpResponseMessage with the BadRequest http status code and the created ODataError as a payload. As an example of very simple code (you can see that it would depend on how you build your custom exception):
public override void OnException(HttpActionExecutedContext filterContext)
{
Exception ex = filterContext.Exception;
HttpRequestMessage currentRequest = filterContext.Request;
if (filterContext.Exception.GetType() == typeof(YourCustomValidationException))
{
var oDataError = new ODataError()
{
ErrorCode = "invalidModel",
Message = "Your model is not valid.",
InnerError = new ODataInnerError()
{
TypeName = ex.TheEntityThatHasErrors
},
};
foreach (var validationError in ex.ValidationErrors)
{
oDataError.InnerError.Message += validationError + ", ";
}
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.RequestMessage = currentRequest;
response.Content = new StringContent(JsonConvert.SerializeObject(oDataError));
filterContext.Response = response;
}
}
Finally, you will have to setup the custom ActionFilterAttribute and the custom ErrorFilterAttribute to be used each time that a request reach your controller. You can decorate your actions, controllers, or you can set the filters for all your API controllers in the WebApiConfig, with config.Filters.Add(...);
You can find more information about all of this in here. In the end, the error and exception handling is the same for ASP.Net Web API, with or without OData; difference is that if you have an OData API, you should return errors in OData style.
Hope all this info is understandable and helps you somehow.

How to return a custom view OR a JSON when the Authorization fail, instead of showing a username and password dialog

I am working on an asp.net mvc 4 web application , and i wrote the following custom authorization class:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : AuthorizeAttribute
{
public string Model { get; set; }
public string Action { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAuthenticated)
return false;
//code goes here
if (!repository.can(ADusername, Model, value)) // implement this method based on your tables and logic
{
return false;
//base.HandleUnauthorizedRequest(filterContext);
}
return true;
// base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new JsonResult();
viewResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
viewResult.Data = (new { IsSuccess = "Unauthorized", description = "Sorry, you do not have the required permission to perform this action." });
filterContext.Result = viewResult;
}
else
{
var viewResult = new ViewResult();
viewResult.ViewName = "~/Views/Errors/_Unauthorized.cshtml";
filterContext.Result = viewResult;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
but the only problem i am facing now is that if the authorization fail then the user will be prompted to enter username and password, although i have override the HandleUnauthorizedRequest to return a view or JSON based on if the request is AJAX or not. so can you advice why the user is being prompted to enter his username and password when the authorization fail, instead of receiving the _unauthorized view or the JSON containing an error message
but the only problem i am facing now is that if the authorization fail
then the user will be prompted to enter username and password,
although i have override the HandleUnauthorizedRequest to return a
view or JSON based on if the request is AJAX or not.
That's because you are absolutely always hitting the following line in your HandleUnauthorizedRequest method:
base.HandleUnauthorizedRequest(filterContext);
You know what this line do? It calls the base method. You know what the base method do? It returns 401 status code. You know what happens when 401 status response code is returned in an ASP.NET application in which you are using Forms Authentication? You get the login page.
So yeah, if you are using AJAX or something and intend to be returning some JSON or something make sure that the base stuff is never called. By the way in your else condition you seem to be attempting to render some ~/Views/Errors/_Unauthorized.cshtml view which obviously is useless once again because you are also calling the base method which will simply redirect to the login page.
I think that at this stage of my answer you already know what to do: get rid of this last line of your HandleUnauthorizedRequest method in which you are throwing all your efforts into the trash by calling the base method.
And if you want to do things properly and return 401 status code and not get the login page but instead return some custom JSON you could use the SuppressFormsAuthenticationRedirect property on the Response object. And if you are using some legacy version of the .NET framework which doesn't have this property you might find the following blog post useful in which Phil Haack explains how to handle this case.

ASP.NET Web API how to authenticate user

I'm trying to create a simple user authentication function but I just can't get it to work.
Here is the code I'm working on:
public class LoginController : ApiController
{
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
public bool Login(string token)
{
//Check token
if (.....)
{
//Authenticate user
var identity = new GenericIdentity("Test user");
SetPrincipal(new GenericPrincipal(identity, new string[]{"Test role"}));
}
}
[Authorize]
public string TestFun()
{
return "Hello " + User.Identity.Name;
}
}
So, if I try to call method TestFun() first, it returns error code 401 like it should.
However when I call method Login() it should somehow save user credentials, but this is where I get lost, I just can't get it to work.
TestFun() always returns error code 401 even if I call Login() first.
If I try to put return "Hello " + User.Identity.Name; in the Login() function it returns correct username, but in the TestFun() the user is not available.
I've even tried using Sessions and FormsAuthentication but I just can't get it to work, even on this really simple example.
Can someone please tell me what am I missing?
Thanks!
The Login method sets the principal for current request only. Just after the request completes, the principal context is wiped out so that the server can handle other requests for other users. When a new request comes, eons later from the server perspective, the principal context no longer exists and if nothing restores it, the request is unauthenticated.
To fix this you have to return something from your login method to the client. Not only bool but rather - an authentication token. Something the client could use to authenticate further requests.
It could be anything. Forms cookie would be fine as long as the client remembers to append it to further requests. Another common practice is to have a custom authentication token returned to the client and then appended by the client in a custom authentication header. And as forms cookies are handled by the Forms Authentication module, custom headers would need a custom mvc authentication filter or custom asp.net authentication module so that the token is readed, the identity is extracted and restored just before the request is about to execute.
If you don't like to bake your own token infrastructure, I would also recommend OAuth2 tokens. There is a great book that contains easy to follow examples on this and other possible authentication methods:
http://www.amazon.com/Pro-ASP-NET-Web-API-Security/dp/1430257822/ref=sr_1_1?ie=UTF8&sr=8-1&keywords=web+api+security
I just got the same issue, yes, I agreed we need to save that principal into somewhere (cookie, session) for other action to use, so, in SetPrincipal function I added
HttpContext.Current.Session["user"] = HttpContext.Current.User;
Now, the issue is how to get it back for other action, the idea popups in my mind is to extend AuthorizeAttribute and override IsAuthrized function, it will read the session first and if it found the session, it will return true, otherwise it will return false.
namespace BinZ
{
public class MyAuthorizeAttribute:AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext) {
HttpContext.Current.User = HttpContext.Current.Session["user"] as IPrincipal;
return HttpContext.Current.User != null;
}
}
}
Please remember to replace [Authorize] to [MyAuthorizeAttribute] in WebApi controller.
It works for me very well.
Cheers

How do you handle ajax requests when user is not authenticated?

How do you handle ajax requests when user is not authenticated?
Someone enters the page, leaves room for an hour, returns, adds comment on the page that goes throuh ajax using jQuery ($.post). Since he is not authenticated, method return RedirectToRoute result (redirects to login page). What do you do with it? How do you handle it on client side and how do you handle it in controller?
EDIT:
I wrote above answer a long time ago and now I believe that sending 403 is not proper way to go. 403 has slightly different meaning and it just shouldn't be used. This is corrected attribute using 401. It differs only with additional context.HttpContext.Response.End() in Http401Result and different HTTP code:
public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
private class Http401Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 401.
context.HttpContext.Response.StatusCode = 401;
context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
context.HttpContext.Response.End();
}
}
private readonly bool _authorize;
public OptionalAuthorizeAttribute()
{
_authorize = true;
}
//OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller.
//That is why parameter is introduced.
public OptionalAuthorizeAttribute(bool authorize)
{
_authorize = authorize;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//When authorize parameter is set to false, not authorization should be performed.
if (!_authorize)
return true;
var result = base.AuthorizeCore(httpContext);
return result;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 401 error.
filterContext.Result = new Http401Result();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
OLD ANSWER:
While I like the ideas posted in other answers (which I had an idea about earlier), I needed code samples. Here they are:
Modified Authorize attribute:
public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
private class Http403Result : ActionResult
{
public override void ExecuteResult(ControllerContext context)
{
// Set the response code to 403.
context.HttpContext.Response.StatusCode = 403;
context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
}
}
private readonly bool _authorize;
public OptionalAuthorizeAttribute()
{
_authorize = true;
}
//OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller.
//That is why parameter is introduced.
public OptionalAuthorizeAttribute(bool authorize)
{
_authorize = authorize;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//When authorize parameter is set to false, not authorization should be performed.
if (!_authorize)
return true;
var result = base.AuthorizeCore(httpContext);
return result;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
//Ajax request doesn't return to login page, it just returns 403 error.
filterContext.Result = new Http403Result();
}
else
base.HandleUnauthorizedRequest(filterContext);
}
}
HandleUnauthorizedRequest is overridden, so it returns Http403Result when using Ajax. Http403Result changes StatusCode to 403 and returns message to the user in response. There is some additional logic in attribute (authorize parameter), because I turn on [Authorize] in the base controller and disable it in some pages.
The other important part is global handling of this response on client side. This is what I placed in Site.Master:
<script type="text/javascript">
$(document).ready(
function() {
$("body").ajaxError(
function(e,request) {
if (request.status == 403) {
alert(request.responseText);
window.location = '/Logout';
}
}
);
}
);
</script>
I place a GLOBAL ajax error handler and when ever $.post fails with a 403 error, the response message is alerted and the user is redirected to logout page. Now I don't have to handle the error in every $.post request, because it is handled globally.
Why 403, and not 401? 401 is handled internally by MVC framework (that is why redirection to login page is done after failed authorization).
What do you think about it?
The idea I came up with when a coworker asked about how to handle it was this - make an AuthorizeAjax attribute. It can interrogate and verify that Request.IsAjaxRequest() and, if the request isn't authenticated, return a specific JSON error object. It's possible you could simply override the default AuthorizeAttribute and have it call the base unless it's an unauthorized AJAX request so you don't have to worry about whether to tag controller actions with [Authorize] or [AuthorizeAjax].
On the client-side, all your pages would have to be equipped to deal with the returned error, but that logic can likely be shared.
I would propose creating your own AuthorizeAttribute and if the request is an Ajax request, throw an HttpException(401/403). And also switch to use jQuery's Ajax Method instead.
Assuming you've implemented error pages and they return the correct status code, the error callback will be executed instead of the success callback. This will be happen because of the response code.
The simplest and cleanest solution I've found for this is to register a callback with the jQuery.ajaxSuccess() event and check for the "X-AspNetMvc-Version" response header.
Every jQuery Ajax request in my app is handled by Mvc so if the header is missing I know my request has been redirected to the login page, and I simply reload the page for a top-level redirect:
$(document).ajaxSuccess(function(event, XMLHttpRequest, ajaxOptions) {
// if request returns non MVC page reload because this means the user
// session has expired
var mvcHeaderName = "X-AspNetMvc-Version";
var mvcHeaderValue = XMLHttpRequest.getResponseHeader(mvcHeaderName);
if (!mvcHeaderValue) {
location.reload();
}
});
The page reload may cause some Javascript errors (depending on what you're doing with the Ajax response) but in most cases where debugging is off the user will never see these.
If you don't want to use the built-in header I'm sure you could easily add a custom one and follow the same pattern.
Here's a solution I use. It is dead simple, if a bit brute-force. I like it because I'm lazy and I don't want to think about special attributes on action methods and I don't want to write ajax error handlers if I don't have to (although there's no reason client script couldn't detect the 403 status code and do something user friendly).
Putting this in Global.axax detects any unauthenticated ajax request and simply returns 403, with no content. This prevents unauthenticated ajax calls getting redirected to the login form when forms authentication is in use.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Prevent Ajax requests from being returned the login form when not authenticated
// (eg. after authentication timeout).
if ((Request.Headers["X-Requested-With"] != null && Request.Headers["X-Requested-With"] == "XMLHttpRequest")
||
(Request["X-Requested-With"] != null && Request["X-Requested-With"] == "XMLHttpRequest"))
{
if (!Request.IsAuthenticated)
{
Response.Clear();
Response.StatusCode = 403;
Response.Flush();
Response.End();
}
}
}
You can detect ajax request and send 401, and on client side you can even show an ajax dialog with login prompt, after which you can "continue" your failed ajax request and make your application work and user feel like session timeout never happened. See this answer for details.

Resources