I have added authentication to my API with the possibility to authenticate with two different authentication schemes.
Based on the format of the Auth header I forward the authentication request to the appropriate Authentication handler using a ForwardDefaultSelector.
services.AddAuthentication(opt =>
{
opt.DefaultScheme = "ForwardScheme";
opt.DefaultChallengeScheme = "ForwardScheme";
})
.AddPolicyScheme("ForwardScheme", "ForwardScheme", options =>
options.ForwardDefaultSelector = context =>
context.Request.IsSchemeA()
? "SchemeA"
: "SchemeB")
.AddSchemeA()
.AddSchemeB();
Adding Schemes:
public static AuthenticationBuilder AddSchemeA(this AuthenticationBuilder builder)
{
builder.AddScheme<AuthenticationSchemeOptions, SchemeAHandler>(
"SchemeA", null);
return builder;
}
The forwarding seems to be working fine, I can see the request coming to the right auth handler based on the header value.
The problem is even when the auth fails, the API call is not blocked and I still get a 200 response back.
In the AuthHandler I am just returning this:
return AuthenticateResult.Fail("Authentication Failed");
Any idea what I am missing here?
Thanks.
If you register an authentication scheme for your application and you add the authentication middleware to the ASP.NET core request pipeline, you are basically asking the ASP.NET core framework of trying to authenticate any incoming request, by using the specified authentication scheme. This won't change, by itself, the response status code from 200 to 401.
In order for you to get a 401 response when an anonymous request gets to your server, you need to raise a so called authetication challenge to the incoming request.
The simplest way to do that is basically requiring the request principal to be authenticated in order to execute a certain action method. To do that you simply need to decorate the action method by using the [Authorize] attribute. This way you are setting an execution policy to your action method, which allows the method execution only if the request principal is authenticated.
#EnricoMassone thanks for pointing me in the right direction.
I was missing [Authorize] attribute on my controller methods.
you can set the attribute individually on each method or you could do something like this, and it would enable authorization on all methods for all of your controllers
Related
I have to implement authorization for my web api using another/external API. So I have to get the JWT from the request and call another API with that token to know whether the user is authorized.
presently my authentication is working, and I am using
IServiceCollection.AddAuthentication().AddJwtBearer() // removed code to set options
in sample above, I have removed code to provide options and setting the TokenValidationParameters. So my auth logic is working as expected.
Now i am looking to implement custom Authorization. In my custom authorization logic i have to make call to another/external API and pass some parameters. The parameters will be different for different action methods in my controller. The external API will return bool (indicating whether authorized or not), I don't have need to maintain any application role/claims in my code.
is using dynamic policy name and string parsing as mentioned in doc the only/recommended option.
So i have to get jwttoken from request and call another API with that token to know if user is authorized or not.
You should try to prevent having to make an an outbound API request for each request your API gets.
It seems like you have an external authentication service which lets your users log in and returns a token of sorts. You need to know how that third party signs their tokens, then obtain some form of (public) key from them.
With this key you can validate whether the token has been signed by the party you trust. For this, you configure the appropriate TokenValidationParameters which you pass to your services.AddAuthentication().AddJwtBearer() so you can let Identity validate their token using their keys.
See:
Authorize with a specific scheme in ASP.NET Core
Microsoft.AspNetCore.Authentication.JwtBearer Namespace
Ultimately you'd also configure some sort of background job that cycles the keys of the external provider when they do, if they do, which they hopefully do.
As for your updated question: you appear to want to use an external service for authorization, i.e. who can do what.
You'll have to implement this yourself. Usually this is done using scopes, which are retrieved once, during authentication. These can contain values like "finance" to give access to financial controllers, or "finance:orders:list finance:products".
[RequiredScope("finance:orders", "finance:orders:list")]
public IActionResult Index()
{
return View();
}
If the API you're talking to does not have a way to retrieve the relevant scopes, claims or permissions during authentication (or once per resource), then you can't, for example, cache the user's roles to your controllers or entities.
You need to realise this will incur extra overhead per API call, as well as your application being down when the authentication/authorization service is down.
If you still want to do this, the most trivial way to do async authorization on a controller would be a policy:
public class AuthorizeWithAuthServiceRequirement : IAuthorizationRequirement
{
public const string PolicyName = "external-authorization-service";
}
public class AuthorizeWithAuthServiceHandler : AuthorizationHandler<AuthorizeWithAuthServiceRequirement>
{
private IYourApiService _yourApiService;
public AuthorizeWithAuthServiceHandler(IYourApiService yourApiService/* your DI here */)
{
_yourApiService = yourApiService;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeWithAuthServiceRequirement requirement)
{
var httpContext = context.Resource as HttpContext
?? throw new ArgumentException("Can't obtain HttpContext");
// Get the user or their claims or the ID from the route or something
var user = httpContext.User;
var claim = user.FindAll("foo-claim");
var allClaims = user.Claims;
var id = httpContext.Request.RouteValues["id"];
// TODO: response and error handling
var isUserAuthorized = _yourApiService.IsUserAuthorized(user, id, entity, claim, ...);
if (!isUserAuthorized)
{
context.Fail(/* TODO: reason */);
}
}
}
You register this with DI like this:
// Register the handler for dependency injection
services.AddSingleton<IAuthorizationHandler, AuthorizeWithAuthServiceHandler>();
// Register the policy
services.AddAuthorization(options =>
{
options.AddPolicy(AuthorizeWithAuthServiceRequirement.PolicyName, x => { x.AddRequirements(new AuthorizeWithAuthServiceRequirement()); });
});
And then apply it to a controller or action method:
[Authorize(Policy = AuthorizeWithAuthServiceRequirement.PolicyName)]
public class FooController : Controller
{
}
If you want more fine-grained control like custom attributes with parameters (like [CustomAuthorization(ApiPermission.Foo)]) per controller or action, or if you want to first load an entity and pass that to the handler, see Ilja in Asp.Net Core: Access custom AuthorizeAttribute property in AuthorizeHandler and their GitHub repository demonstrating three different approaches.
I'm using multiple JWT Bearer authentication as documented by Microsoft
It all works fine, up until the point where I put it into an application that has controller methods that need optional authentication (a property in appsettings.json controls whether the request needs to be authenticated or not)
I'm doing the optional authentication using my own attribute that implements IAsyncAuthorizationFilter
public class OptionalAuthorizationAttribute: Attribute, IAsyncAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
SetUnauthorized(context);
}
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
SetUnauthorized(context);
return Task.CompletedTask;
}
private void SetUnauthorized(AuthorizationFilterContext context)
{
var configService = (IConfiguration)context.HttpContext.RequestServices.GetService(typeof(IConfiguration));
if (configService != null)
{
var enableAuth = configService.GetValue<bool>("AppSettings:EnableAuthentication");
if (enableAuth)
{
if (user?.Identity?.IsAuthenticated != true)
{
context.Result = new UnauthorizedResult();
}
}
}
else
{
context.Result = new UnauthorizedResult();
}
}
}
When I use a single authentication scheme, this works like a charm. When I use two schemes, it works for the default scheme ('Bearer', but not my second scheme). Looking at the logs, I see this
2021-07-19 14:50:41.616 +02:00 [Information]
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Failed
to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenUnableToValidateException:
IDX10516: Signature validation failed. Unable to match key:
Now, as I understand that's normal when the first jwt scheme cannot validate the token.. the second can. But, in my OptionalAuthorizationAttribute context.HttpContext.User.Identity is now unauthenticated, so the request is still rejected. I'm guessing that the User.Identity is being populated by what is gotten from the token after it goes through the default scheme (which cannot validate the token so it's an empty ClaimsIdentity and unauthorized.
Any chance I can get the Identity from the second authentication scheme instead? (and make this generic in case I ever need to add additional auth schemes).
Things to work just fine if I swap out my [OptionalAuthorization] tag on the controller and replace it with [Authorize] so I know that my setup is complete.. it's just that my attribute gets triggered before the token has been validated using the second scheme.
Here's a controller showing how I'm using my OptionalAuthorizationAttribute
[Route("[controller]")]
[ApiController]
public class WebDirectoryController:ControllerBase
{
[HttpGet]
[OptionalAuthorization]
[Route("Sources")]
public IActionResult GetPlugins()
{
... implementation
}
}
I have been digging into this topic and after reading through this wanted to respond with my perspective of how to handle authenticating with multiple JWT tokens.
I assume the code you are sharing is a class that defines a custom auth policy. Through my research I see official documentation using AuthorizationPolicyBuilder: Use multiple authentication schemes
I dont see why you cant just determine if you want to add the optional auth policy using the configuration value at startup following the microsoft doc linked above.
I am writing a .net Core 2.0 Web API controller that performs file upload using a multipart type http request and is based in the streaming technique described here.
At this point I have to say that I if you want you can skip the next two paragraphs that describe the reason that led me to the need for a solution to the problem that is described after the two paragraphs.
I initially thought of authenticating the user by sending authentication data in the first section of the multipart request and validating the user as soon as the user data are read, by contacting the database and performing the proper request. However, I thought that since this is a streaming request, any delay in authenticating the user using the database, would delay reading the stream with the file. This would cause the TCP receive buffer to fill with data (possibly also increase its size) and would defeat the purpose of streaming the file (instead of buffering), since memory consumption for this connection would increase.
In order to get rid of this issue I thought of using a 2 step authentication using JWTs. The Web API user will first perform a request and ask for a JWT. Then it would use this JWT in the upload request. As I understand it, JWT authentication should be much faster than a database request since it is performed by validating the JWT using the key stored in the server, so the previous issue should not exist.
I implemented the JWT authentication for the upload request following this very good description from Auth0 and it worked just fine. More specifically the controller has an [Authorize] attribute that forces Web API to to authenticate the user by validating the JWT before the controller is executed.
The problem I am facing is that with the above proposed solution when an unauthorized user tries to upload a file the Controller action is never called. The Authentication engine returns an Unathorized (401) response to the user and lets the user continue sending file data. The last part is my problem. I would like unauthorized users, which are probably attackers, to receive the 401 response and then have their connection terminated.
So, what I want is to keep the authentication/authorization part as it already works and also terminate the user connection after sending the 401 response. I know (and have also tested it) that from inside a controller action method an http connection can be terminated by calling
HttpContext.Abort();
I suspect that by using a filter, I could do what I want but I am not very familiar with filters so that is why I am asking.
We can achieve that by using an IAuthorizationFilter.
Inside it, we gonna set an special ActionResult called AbortUnauthorizedConnectionResult and in that we set the Status Code to 401 and Content-Length to 0 and by calling Response.Body.Flush() we make sure it's sent to client before we call Abort().
Here we have an AuthorizationFilter called AbortUnauthorizedConnections:
class AbortUnauthorizedConnections : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context.HttpContext.User?.Identity == null || !context.HttpContext.User.Identity.IsAuthenticated)
{
// by setting this we make sure the pipe-line will get short-circuited.
context.Result = new AbortUnauthorizedConnectionResult();
}
}
}
And because we have inherited from Attribute we can use it on the upload action like this:
[Authorize]
[AbortUnauthorizedConnections]
public async Task<IActionResult> UploadFile()
{
// we do whatever we want.
}
Here is the code for AbortUnauthorizedConnectionResult:
class AbortUnauthorizedConnectionResult : StatusCodeResult
{
public AbortUnauthorizedConnectionResult() : base(401)
{
}
public override async Task ExecuteResultAsync(ActionContext context)
{
await base.ExecuteResultAsync(context);
context.HttpContext.Response.Headers.Add("Content-Length", "0");
context.HttpContext.Response.Body.Flush();
context.HttpContext.Abort();
}
}
Now if an unauthorized user try to access this controller will get 401 and it's connection gets aborted.
This is the solution I actually implemented due to its simplicity, following #Tratcher's advice:
First, I deleted the [Authorize] attribute from my Controller Action method. Then I wrote the beginning of my Controller Action method as follows:
public async Task<string> UploadFile()
{
if (!(await HttpContext.AuthenticateAsync()).Succeeded)
{
HttpContext.Response.StatusCode = 401; //Unauthorized
HttpContext.Response.Headers.Add("Content-Length", "0");
HttpContext.Response.Body.Flush();
HttpContext.Abort();
return null;
}
...
}
In my webapi application created from template in VS2013 I have added custom OAuthBearerAuthenticationProvider class in Startup.Auth.cs file:
public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
public override Task ValidateIdentity(OAuthValidateIdentityContext context)
{
UserManager<ApplicationUser> userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user = userManager.FindById(context.Ticket.Identity.GetUserId());
var claims = context.Ticket.Identity.Claims;
if (claims.FirstOrDefault(claim => claim.Type == "AspNet.Identity.SecurityStamp") == null
|| claims.Any(claim => claim.Type == "AspNet.Identity.SecurityStamp"
&& !claim.Value.Equals(user.SecurityStamp)))
{
context.Rejected();
}
return Task.FromResult<object>(null);
}
}
Also I have added the variable:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
And in the ConfigureAuth(IAppBuilder app) method I have added the following lines of code to use the custom OAuthBearerAuthenticationProvider class:
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
OAuthBearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider;
OAuthBearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode;
OAuthBearerOptions.AuthenticationType = OAuthOptions.AuthenticationType;
OAuthBearerOptions.Description = OAuthOptions.Description;
OAuthBearerOptions.Provider = new CustomBearerAuthenticationProvider();
OAuthBearerOptions.SystemClock = OAuthOptions.SystemClock;
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
All those changes I have made to implement my own custom logic of bearer token verification. For some reason the SecurityStamp verification is not implemented in Webapi application created from template in VS 2013. I thought this should have been done by default.
To verify the SecurityStamp validation concept I have changed SecurityStamp in the database and after that called some webapi method from client using old bearer token, i.e. containing old SecurityStamp claim. Please note my webapi controller is annotated with [Authorize] attribute. After that ValidateIdentity(OAuthValidateIdentityContext context) method has been called and context.Rejected() line has been executed and I was expecting the webapi method should not be called after that and the 401 Unauthorized response should be send back to the client.
But nothing of this happened. Webapi method did get called and the client did successfully get sensitive data from server whereas should not, because the old bearer token the client sent to the server for authentication and authorization must not be valid after password change.
I thought if context.Rejected() have been called in ValidateIdentity method any [Authorize] decorated webapi method should not be called and the
client should receive something like 401 Unauthorized response.
Am I misunderstanding the whole thing? If I am could anyone explain how it works, please? Why after context.Rejected() has been called the [Authorize] annotated controller's webapi method gets called and successfully returns sensitive data? Why the 401 Unauthorized response has not been sent instead? How to achieve the goal which is the 401 Unauthorized response to be sent back to the client when SecurityStamp claim is not the same as in the database currently?
Finally I was able to find the explanation of how the things works. It is the Hongye Sun's comment to his answer to the stackoverflow question:
How do you reject a Katana Bearer token's identity
I cite it here:
"UseOAuthBearerTokens will register Bearer authentication middleware and authorization server middleware into the pipeline. If you call both methods, you will register two Bearer auth middlewares. You need to call UseOAuthAuthorizationServer to register authorization server only."
So I replaced this line of code:
app.UseOAuthBearerTokens(OAuthOptions);
To this one:
app.UseOAuthAuthorizationServer(OAuthOptions);
And things started to work as they should. I. e. the [Authorize] annotated controller's webapi method not called and the 401 Unauthorized response sent back after context.Rejected() has been called.
I registered a MessageHandler (with config.MessageHandlers.Add(new ValidationHandler()) which inherits from DelegatingHandler. It checks each Request for a security token and checks if it is valid.
I got 2 or 3 actionmethods in my Controller which should be accessabel without any authorization.
My Problem:
The MessageHandler is called first. So the actionmethod which should be accessabel from everywhere will be handled as a unauthorized request.
I'm not abel to change the code of the MessageHandler.
I tried to add the allowanonymous attribute, but i still get an unauthorized response.
I found this post Redirecting unauthorized controller in ASP.NET MVC . So my current idea would be to forward the user on the HandleUnauthorizedRequest to the proper action method. But I think it's not the best way.
Is there a better way for this? Is there a way to tell the web.config that actionmethod1 and actionmethod2 are allowed to be accessed as Unauthorizeded user?
[Edit]
Creating an UnAuthorizeAttribute with the AuthorizeAttribute which forwards the user still to the action methods doesn't work. The messagehandler "kills" the request with
statusCode = HttpStatusCode.Unauthorized;
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
So the UnAuthorizeAttribute will not be invoked. I'm using asp.net mvc webapi
You can try creating actionfilter to handle this as you know the request you are getting into the controller / action. This is just and idea.
http://www.codeproject.com/Articles/650240/A-Simple-Action-Filter-Overview
Hope this helps.