Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make Web API authentication return 401 instead of redirect to login page

I have Web API with OWIN Authentication in Web MVC. I'm using <authentication> in Web.Config for my Web MVC so it's redirecting to login page.

<authentication mode="Forms">
    <forms name="WEB.AUTH" loginUrl="~/login" domain="" protection="All" 
    timeout="43200" path="/" requireSSL="false" slidingExpiration="true" />
</authentication>

I'm using [System.Web.Http.Authorize] attribute to authorize my Web API. But somehow, the API redirecting to login page same like my MVC app because of above configuration.

what I want to do is keep redirecting function for the Web MVC but returning 401 for Web API. How can I achieve this? should I create a custom authorization attribute for Web API?

--EDIT--

I found the answer from this post SuppressDefaultHostAuthentication in WebApi.Owin also suppressing authentication outside webapi

So I just add a few lines into my Startup.cs. I had all my controllers configured with a "api" prefix route.

HttpConfiguration config = new HttpConfiguration();
//..some OWIN configuration
app.Map("/api", inner =>
{
  inner.UseWebApi(config);
});

make sure you put app.Map() after Web Api Configuration lines. Otherwise, it will give error to MVC application.

like image 510
vantian Avatar asked Jan 19 '16 15:01

vantian


4 Answers

Create a custom AuthorizeAttribute:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Unauthorized");
    }
}

If you in the future skip the web.config stuff and use owin to setup your authentication, you could in your Startup.cs do:

var provider = new CookieAuthenticationProvider();
var originalHandler = provider.OnApplyRedirect;
provider.OnApplyRedirect = context =>
{
    if (!context.Request.Uri.LocalPath.StartsWith(VirtualPathUtility.ToAbsolute("~/api")))
    {
        context.RedirectUri = new Uri(context.RedirectUri).PathAndQuery;
        originalHandler.Invoke(context);
    }
};

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    CookieName = FormsAuthentication.FormsCookieName,
    LoginPath = new PathString("/Account/LogOn"),
    ExpireTimeSpan = TimeSpan.FromMinutes(240),
    Provider = provider
});
like image 50
peco Avatar answered Oct 21 '22 19:10

peco


In .NET Core I have solved it like this, Startup.cs:

    public void ConfigureServices(IServiceCollection services)
    {
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(options =>
            {
                options.Cookie.SameSite = SameSiteMode.Strict;
                options.Cookie.Name = "AuthCookie";
                options.Events.OnRedirectToAccessDenied = UnAuthorizedResponse;
                options.Events.OnRedirectToLogin = UnAuthorizedResponse;
            })
    ....
    }

    internal static Task UnAuthorizedResponse(RedirectContext<CookieAuthenticationOptions> context)
    {
        context.Response.StatusCode = (int) HttpStatusCode.Unauthorized;
        return Task.CompletedTask;
    }
like image 22
Oyvind Habberstad Avatar answered Oct 21 '22 19:10

Oyvind Habberstad


This is what worked for me.

Creating a custom attribute:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class NoRedirectAuthorizeAttribute : AuthorizeAttribute
{        
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
    }
}

Using the attribute in your controller:

    [HttpDelete]
    [NoRedirectAuthorizeAttribute(Roles = "Admin")]
    [Route("api/v3/thingstodelete/{id=id}")]
    public IHttpActionResult DeleteThingToDelete(Guid id)
    {
      //delete code
    }

Here are just overriding the HandleUnauthorizedRequest method of the AuthorizeAttribute. So, instead of sending a redirect (304) to the login page, we send Forbidden(403) HTTP status code.

like image 23
Sudarshan_SMD Avatar answered Oct 21 '22 19:10

Sudarshan_SMD


I struggled with this issue and I came up with a way to only do the redirect if I didn't find the token that I use in the header for my custom manual authorization of my WebApi. This is my setup (notice the Provider object and OnApplyRedirect action)

    app.UseCookieAuthentication(new CookieAuthenticationOptions
   {
     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
     LoginPath = new PathString("/Account/Login"),
     ExpireTimeSpan = TimeSpan.FromMinutes(30),
     Provider =  new CookieAuthenticationProvider
      {
        OnApplyRedirect = (ctx) => {
            var token = HttpContext.Current.Request.Headers.Get("X-User-Token");
            if (token == null) ctx.Response.Redirect(ctx.RedirectUri);
         }
      }
   });  
like image 22
Yasser Jarouf Avatar answered Oct 21 '22 19:10

Yasser Jarouf