In my ASP.NET Core project I got a few API-Controllers with jwt-authorization like this:
[Route("api/v1/[controller]")]
public class MyController : Controller
{
[HttpGet("[action]")]
[Authorize(Policy = MyPolicy)]
public JsonResult FetchAll()
{
}
}
When authorization for accessing the action FetchAll() Fails I want HttpStatusCode.Forbidden as response. Instead Mvc does a reroute to Account/Login?ReturnUrl=[...]
I tried to capture the Redirect-Events and return Forbidden/Unauthorized overriding the Cookie Events to no avail:
app.UseIdentity();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = TokenController.DummyKey,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters,
});
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AutomaticAuthenticate = false,
AutomaticChallenge = false,
AuthenticationScheme = "BsCookie",
CookieName = "access_token",
TicketDataFormat = new CustomJwtDataFormat(SecurityAlgorithms.HmacSha256, tokenValidationParameters),
Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = context =>
{
if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == (int)HttpStatusCode.OK)
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
else
context.Response.Redirect(context.RedirectUri);
return Task.FromResult(0);
},
OnRedirectToAccessDenied = context =>
{
if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == (int)HttpStatusCode.OK)
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
else
context.Response.Redirect(context.RedirectUri);
return Task.FromResult(0);
}
},
});
Both Events are never called and the Visual Studio output Shows that fetchall Fails and Account/Login will be returned instead:
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:6460/api/v1/Lehrer/GetAll application/json
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: Successfully validated the token.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Bearer.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed for user: (null).
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware:Information: AuthenticationScheme: Bearer was forbidden.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware:Information: AuthenticationScheme: Identity.Application was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action Sam.Learning2.Controllers.LehrerController.GetAll (Sam.Learning2) in 49.7114ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 121.6106ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:6460/Account/Login?ReturnUrl=%2Fapi%2Fv1%2FLehrer%2FGetAll
I want my APIs to return 401/403 instead of redirecting to Login - how do I achieve this when above code does not work?
Microsoft's web api stack is set up to do this out-of-the-box. The solution is at the client end.
Add this header to the client request:
Web api looks for that header. When present it returns a 401 if the request is unauthenticated. When the header is absent it returns the redirect to the login page.
See this https://github.com/aspnet/Security/issues/1394#issuecomment-326445124
I think you only need the more complex code in the cookie events if you cannot modify the client.
Update ASP.NET Core 2.x
The authorization changed a little in ASP.NET Core 2.0. The answer below ist just valid for ASP.NET Core 1.x. For ASP.NET Core 2.0 refer to this answer and this GitHub annoucement.
ASP.NET Core 1.x
What you seems to have forgotten is that
app.UseIdentity()
also registers the cookie middleware.and the ASP.NET Core Identity sets the
AutomaticChallange
totrue
for cookie (ApplicationCookie
) middleware (see source). Hence the redirect to/Account/Login?ReturnUrl
. You will need do disable this option in Identity.If you really want have Identity's Auth (login to web page) and JWT, you'd need to register the middlewares based on the url. So i.e.
app.UseIdentity()
is only registered for non-api urls and Jwt middleware is only registered for urls starting with/api
.You can do that with
.MapWhen
(docs).Now
branch.UseIdentity()
will only be used, for URLs which don't start with/api
, which usually are your MVC views where the redirect to/Account/Login
is desired.I just use Barry Dorrans Asp Net Authorization Workshop
in
ConfigureServices
I just addservices.AddAuthorization();
.and in
Configure
add this code:In Mvc reroute to Account/Login?ReturnUrl=[...] and in API you will get 401 or 403.