Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net Core action does not return 401 when using Authorize attribute on a thread without a principle?

I wanted to add a principle onto the thread by myself , without using the Identity mechanism which really reminds me the old membership/forms authentication mechanics.

So I've managed (successfully) to create a request with principle :

MyAuthMiddleware.cs

public class MyAuthMiddleware
{
    private readonly RequestDelegate _next;


    public MyAuthMiddleware(RequestDelegate next )
    {
        _next = next;

    }


    public async Task Invoke(HttpContext httpContext)
    {
        var claims = new List<Claim>  
        {  
            new Claim("userId", "22222222")  
        };  
        ClaimsIdentity userIdentity = new ClaimsIdentity(claims ,"MyAuthenticationType");  
        ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);

         httpContext.User = principal;
         await _next(httpContext);
    }
}

The Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            //app.UseAuthentication(); //removed it. I will set the thread manually
            if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

            app.UseMyAuthMiddleware(); // <---------- Myne

            app.UseMvc();
            app.Run(async context => { await context.Response.WriteAsync("Hello World!"); });
        }

And here is the Action in the Controller: (Notice Authorize attribute)

    [HttpGet]
    [Route("data")]
    [Authorize]
    public IActionResult GetData()
    {

        var a=User.Claims.First(f => f.Type == "userId");
        return new JsonResult(new List<string> {"a", "b",a.ToString() , User.Identity.AuthenticationType});
    }

Ok Let's try calling this method, Please notice that this does work :

enter image description here

So where is the problem 😊?

Please notice that there is an [Authorize] attribute. Now let's remove
setting principle on the thread ( by removing this line ) :

//httpContext.User = principal; // line is remarked

But now when I navigate to :

http://localhost:5330/api/cities/data

I'm being redirected to :

http://localhost:5330/Account/Login?ReturnUrl=%2Fapi%2Fcities%2Fdata

But I'm expecting to see Unauthorized error.

I'm after WebApi alike responses. This is not a website but an API.

Question:

Why don't I see the Unauthorized error ? And how can I make it appear?

Nb here is my ConfigureServices:

 public void ConfigureServices(IServiceCollection services)
        {
             services.AddAuthentication( CookieAuthenticationDefaults.AuthenticationScheme ) 
                  .AddCookie( CookieAuthenticationDefaults.AuthenticationScheme,  a =>
                  {
                      a.LoginPath = "";
                      a.Cookie.Name = "myCookie";


                  });

            services.AddMvc();
        }

EDIT

Currently What I've managed to do is to use OnRedirectionToLogin :

enter image description here

But it will be really disappointing if that's the way to go. I'm expecting it to be like webapi.

like image 942
Royi Namir Avatar asked Feb 10 '19 14:02

Royi Namir


People also ask

How does Authorize attribute work in ASP.NET Core?

Authorization in ASP.NET Core is controlled with AuthorizeAttribute and its various parameters. In its most basic form, applying the [Authorize] attribute to a controller, action, or Razor Page, limits access to that component to authenticated users. Now only authenticated users can access the Logout function.

How does the Authorize attribute work?

If a user is not authenticated, or doesn't have the required user name and role, then the Authorize attribute prevents access to the method and redirects the user to the login URL. When both Roles and Users are set, the effect is combined and only users with that name and in that role are authorized.


1 Answers

The default implementation of the OnRedirectToLogin delegate looks like this:

public Func<RedirectContext<CookieAuthenticationOptions>, Task> OnRedirectToLogin { get; set; } = context =>
{
    if (IsAjaxRequest(context.Request))
    {
        context.Response.Headers["Location"] = context.RedirectUri;
        context.Response.StatusCode = 401;
    }
    else
    {
        context.Response.Redirect(context.RedirectUri);
    }
    return Task.CompletedTask;
};

As is clear from the code above, the response that gets sent to the client is dependent upon the result of IsAjaxRequest(...), which itself looks like this:

private static bool IsAjaxRequest(HttpRequest request)
{
    return string.Equals(request.Query["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal) ||
        string.Equals(request.Headers["X-Requested-With"], "XMLHttpRequest", StringComparison.Ordinal);
}

This means that the response will be a 401 redirect if either a X-Requested-With request header or query-string value is set to XMLHttpRequest. When you hit your endpoint directly from the browser or from within e.g. Fiddler, this value is not set and so the response is a 302, as observed. Otherwise, when using XHR or Fetch in the browser, this value gets set for you as a header and so a 401 is returned.

like image 113
Kirk Larkin Avatar answered Nov 01 '22 17:11

Kirk Larkin