.Net, Azure and occasionally gamedev

Return 401 unauthorized from an api in .Net Core

2018/08/11

For my homeapp I built a website that had both a frontend (via razor pages) and an api.

Both require (some) authorization. E.g. the frontend allows you to manage your account, so you login via any supported oauth provider to see your information. The api routes allow the use of json web tokens so that clients such as the raspberry (which has no user context) can connect.

The authorization scheme is slightly different for these two, so usually in professional environments these two (frontend and api) would be split into separate projects and then served from different domains (e.g. api.* subdomain) or served as a separate application in IIS via the /api path. Thus authentication would need to be setup separately (api exclusively uses jwt and frontend exclusively uses oauth) and all would be well.

Since my entire project is really small I didn't want to split frontend and api and instead I combined them into the same project which is supported by .Net Core and works great (for the most part).

Eventually I however stumbled upon a small bug in my application which is the reason for this blogpost:

One of my apis is supposed to return 202 "Accepted" when the request is processed and yet it returned 200 "Ok". I quickly figured out that the reason was that my request had no authentication and thus was redirected to the login page which itself is a 200 "ok" page with content telling the user to click login. Not quite what you want from an api.

A quick search brought me to this similarly titled blogpost which shows a solution. However because .Net Core has evolved a bit since the post was made the solution didn't quite fit.

I tried all the various options of setting the OnRedirectToLogin method in all the available cookie setups:

app.UseCookieAuthentication(o => { .. });
services.AddAuthentication().AddCookie(o => { .. });
services.ConfigureApplicationCookie(o => { .. });

but none of them had any effect when I set the options.Events property.

I then found this guy who figured out another way by using dependency injection and that worked for me as well.

Instead of setting the instance, you can also specify the type which is then loaded via DI.

services.ConfigureApplicationCookie(options =>
{
    // https://devblog.dymel.pl/2016/07/07/return-401-unauthorized-from-asp-net-core-api/
    // and https://stackoverflow.com/questions/48568055/net-core-2-0-cookie-events-onredirecttologin
    options.EventsType = typeof(CustomCookieAuthenticationEvents);
});

with the small implementation that sets status code of any api call:

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
    public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> ctx)
    {
        if (ctx.Request.Path.StartsWithSegments("/api") &&
            ctx.Response.StatusCode == (int)HttpStatusCode.OK)
        {
            ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        }
        else
        {
            ctx.Response.Redirect(ctx.RedirectUri);
        }
        return Task.CompletedTask;
    }
}

finally I configured my api error pages to look different than the normal ones:

app.UseStatusCodePages(async context =>
{
    if (context.HttpContext.Request.Path.StartsWithSegments("/api"))
    {
        // fallback when no content is provided in an api response
        if (!context.HttpContext.Response.ContentLength.HasValue ||
            context.HttpContext.Response.ContentLength == 0)
        {
            context.HttpContext.Response.ContentType = "text/plain";
            await context.HttpContext.Response.WriteAsync(
                  $"Status Code: {context.HttpContext.Response.StatusCode}");
        }
    }
    else
    {
        context.HttpContext.Response.Redirect($"/Error?code={context.HttpContext.Response.StatusCode}");
    }
});

Now my api returns proper status codes while integrated into the Razor pages project that also serves the frontend.

tagged as .Net Core and MVC