I have some endpoints decorated with the [Authenticate] attribute. Now a third party client has to access the same endpoint by using a shared API key.
As the code would be exactly the same for the two cases, I would like to check first if the current request comes from an authenticated user and, if not, checks as fallback if a valid API key is provided.
Is there a way to use both [Authenticate] and [ValidateApiKey] attributes for the same endpoint?
Something like:
[Authenticate | ValidateApiKey]
public long Post(MyDto request)
{
// ....
}
Attributes can only be combined to add functionality, i.e. they can't be used as a fallback or a switch. To get the desired behavior your [ValidateApiKey] attribute should perform the validation fallback as part of its implementation, e.g:
public class ValidateApiKeyAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object reqDto)
{
var session = req.GetSession();
if (session == null || !session.IsAuthenticated)
{
//If not a valid key, execute the `[Authenticate]` attribute
//to handle failed response
if (!CheckValidApiKey(req))
new AuthenticateAttribute().Execute(req,res,reqDto);
}
}
}
Note: Responses should be reference types (e.g. DTO's) or raw strings not value types.
public object Post(MyDto request)
{
// ....
}
Related
I am working with JWT tokens in my backend and my question is how I can read the value of the authorization header, when I have a [Authorize] annotation above my method. I need the token, because in its payload a user id is saved and I need the ID to perform several actions (of course the token gets verified first).
In my frontend, I am adding the authorize header as the following:
axios.post(finalurl, {accessToken: localStorage.accessToken}, {headers: {
'Authorization': `Bearer ${(localStorage.accessToken)}`
}})
In my backend:
[Authorize]
[HttpPut("{id}")]
public async Task<ActionResult<UserWithToken>> PutUser(int id, [FromForm] User user)
{
// ....
}
I know that it would also be possible to transfer the token as an object in the body, but this would make the thing more complicated, because when I am using this way I always have to create new Models that inherit from the object I want to transfer and gets an additional token attribute.
If you simply want to get the user ID and it's stored as a claim in the token, you can get it through the HttpContext like so:
HttpContext.User?
.Claims?
.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Sub)?
.Value
Of course, if you use a different claim type, you can replace JwtRegisteredClaimNames.Sub with whatever your claim is called.
HttpContext.User is the ClaimsPrincipal of the user associated with the current request.
try this:
public async Task<ActionResult<UserWithToken>> PutUser(int id, [FromForm] User user)
{
var token = string.Empty;
var header = (string)HttpContext.Request.Headers["Authorization"];
if (header != null) token = header.Substring(7);
.....
}
You can also try the following method by adding a parameter [FromHeader] which is working
[Authorize]
[HttpPut("{id}")]
public async Task<ActionResult<UserWithToken>> PutUser(int id, [FromForm] User user, [FromHeader] string authorization)
{
// ....
}
In my program I want to
Check if users are registered in a certain users table for all possible actions.
Just for certain actions, I want also to check if the user has the appropriate CRUD rights.
I check the 1st condition in startup with the following code:
services.AddAuthorization(options =>
{
// This policy checks if a user is registered in our Users table.
options.AddPolicy(
"UserIsRegistered",
new AuthorizationPolicyBuilder()
.AddRequirements(new RegistrationRequirement())
.Build());
});
I also add
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers().RequireAuthorization("UserIsRegistered");
});
and I define the RegistrationRequirement and the RegistrationAuthorizationHandler accordingly.
For the 2nd condition, I created an attribute CrudAuthorizeAttribute with its CrudAuthorizationHandler. Since I have one different police for each possible Crud action, I created a CrudPolicyProvider.
Then I registered everything in Startup:
services.AddTransient<IAuthorizationHandler, RegistrationAuthorizationHandler>();
services.AddTransient<IAuthorizationHandler, CrudAuthorizationHandler>();
services.AddTransient<IAuthorizationPolicyProvider, CrudPolicyProvider>();
Now I see when I am checking the 1st condition I'm getting the policy from CrudPolicyProvider but I expect this policy generator to provide policies only in the 2nd condition.
Therefore:
Is there any way to specify that CrudPolicyProvider has to be used only with the CrudAttribute and nowhere else?
More generally, can I define two policy providers (as I can do with IAuthorizationHandler)
services.AddTransient<IAuthorizationPolicyProvider, FirstPolicyProvider>();
services.AddTransient<IAuthorizationPolicyProvider, SecondPolicyProvider>();
and use each one only when required?
If the answer is no, does it mean that I have to define a single GeneralPolicyProvider and inside this provider check which policies I have to provide each time depending on the situation I am? (something like using the POLICY_PREFIX here?).
Thanks!
can I define two policy providers (as I can do with IAuthorizationHandler)and use each one only when required
You could. But the two separated policy providers won't be activated correspondingly. You might have multiple policy providers, but only one of them will be used at the same time. Also there's no magic when using IAuthorizationPolicyProvider.
define a single GeneralPolicyProvider .... check which policies I have to provide each time depending on the situation I am? (something like using the POLICY_PREFIX here?).
Yes. But you don't have to use Policy_Prefix everywhere. In that way you'll repeat yourself too many times.
A better way is to inject a IHttpContextAccessor service into GeneralPolicyProvider, such that you can check the current EndPoint/HttpContext at run-time, and then you can resolve the target Policy Provider dynamically.
An implementation looks like:
public class GenericPolicyProvider : IAuthorizationPolicyProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
private AuthorizationOptions _authZOpts { get; }
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
private IAuthorizationPolicyProvider _fstPolicyProvider {get;set;}
private IAuthorizationPolicyProvider _sndPolicyProvider {get;set;}
public GenericPolicyProvider(IHttpContextAccessor httpContextAccessor, IOptions<AuthorizationOptions> options)
{
this._httpContextAccessor = httpContextAccessor;
this._authZOpts = options.Value;
this.FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
this._fstPolicyProvider = new FirstPolicyProvider(options.Value);
this._sndPolicyProvider = new SecondPolicyProvider(options.Value);
}
// use the target provider to provide policy
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
var targetPolicyProvider = this.GetPolicyProvider(policyName);
return targetPolicyProvider.GetPolicyAsync(policyName);
}
// get the target provider dynamically
private IAuthorizationPolicyProvider GetPolicyProvider(string policyName)
{
var httpContext = this._httpContextAccessor.HttpContext;
if(httpContext==null) throw new Exception("HttpContext must not be null");
// now you get the HttpContext
// check HttpContext to determine which policy provider should be used
// ...
// or check endpoint,e.g. get a mark filter by endpoint.Metadata.GetMetadata<...>()
var endpoint = httpContext.GetEndpoint();
var someMarker = endpoint.Metadata.GetMetadata<SomeMarker>();
// in short, resolve the policy provider dynamically:
if(shouldUseFirstPolicyProvider())
return this._fstPolicyProvider;
else if(shouldUseSecondPolicyProvider())
return this._sndPolicyProvider;
else
return this.FallbackPolicyProvider;
}
...
}
Finally, don't forget to register this GenericPolicyProvider in Startup.
I have an action in my ASP.Net Core WebAPI Controller which takes one parameter. I'm trying to configure it to be able to call it in following forms:
api/{controller}/{action}/{id}
api/{controller}/{action}?id={id}
I can't seem to get the routing right, as I can only make one form to be recognized. The (simplified) action signature looks like this: public ActionResult<string> Get(Guid id). These are the routes I've tried:
[HttpGet("Get")] -- mapped to api/MyController/Get?id=...
[HttpGet("Get/{id}")] -- mapped to api/MyController/Get/...
both of them -- mapped to api/MyController/Get/...
How can I configure my action to be called using both URL forms?
if you want to use route templates
you can provide one in Startup.cs Configure Method Like This:
app.UseMvc(o =>
{
o.MapRoute("main", "{controller}/{action}/{id?}");
});
now you can use both of request addresses.
If you want to use the attribute routing you can use the same way:
[HttpGet("Get/{id?}")]
public async ValueTask<IActionResult> Get(
Guid id)
{
return Ok(id);
}
Make the parameter optional
[Route("api/MyController")]
public class MyController: Controller {
//GET api/MyController/Get
//GET api/MyController/Get/{285A477F-22A7-4691-AA51-08247FB93F7E}
//GET api/MyController/Get?id={285A477F-22A7-4691-AA51-08247FB93F7E}
[HttpGet("Get/{id:guid?}"
public ActionResult<string> Get(Guid? id) {
if(id == null)
return BadRequest();
//...
}
}
This however means that you would need to do some validation of the parameter in the action to account for the fact that it can be passed in as null because of the action being able to accept api/MyController/Get on its own.
Reference Routing to controller actions in ASP.NET Core
I would like to configure one (and only one) of my Controller to accept only application/xml requests.
In the past i used IControllerConfiguration to do that like described here (Per-Controller configuration).
How can i do that in Aspnet Core ?
You can use the Consumes-Annotation together with the accepted content type on Controller or Action level.
With
[Consumes("application/xml")]
public class MyController : Controller
{
public IActionResult MyAction([FromBody] CallModel model)
{
....
}
}
calls to this controller will only succeed if the client provides Content-Type header of application/xml. Otherwise a 415 (Unsupported Media Type) will be returned.
You may simply check Request AcceptTypes / Content-Type headers (like if request.AcceptTypes.Contains("application/xml")) and stop request processing.
Accordingly to link you provided, seems like you just want to ignore content type and always return an XML result. In this case you may use a new Produces attribute.
A filter that specifies the expected System.Type the action will return and the supported response content types. The Microsoft.AspNetCore.Mvc.ProducesAttribute.ContentTypes value is used to set Microsoft.AspNetCore.Mvc.ObjectResult.ContentTypes.
Apply attribute to your controller
[Produces("application/xml")]
public YourXmlController : Controller { }
or only to specific controller action:
[Produces("application/xml")]
public Object ControllerAction()
{
return new { text = "hello world" };
}
Note, that XML formatter does not enabled by default, so you should add one using MvcOptions:
services.Configure<MvcOptions>(options =>
{
//options.InputFormatters.Add( ... );
//options.OutputFormatters.Add( ... );
});
Maybe I'm thinking about this wrong, but I'm trying to create a custom attribute for our CMS to handle auth checks.
https://gist.github.com/sitefinitysteve/62ab761256a64a84d8a6#file-sitefinityjwt-cs-L39
So if this service is called from within the CMS from a logged in user, user data is all there for the service method already.
But in the context of being called from an app, the user is technically Anonymous, however I can decode the token and get the user just fine... but not sure how to like pass that over to the service.
Am I just maybe looking at this wrong, and the proper thing to do is to call a CMS API method to just log that person in (seems slow if I already have the persons user object from line 33, and the service context expires instantly.
Use Request.Items Dictionary
You would use the IRequest.Items dictionary for any data you want to pass throughout ServiceStack's Request Pipeline:
//RequestFilter:
req.Items["info"] = new MyRequestInfo { ... };
In Service:
var info = (MyRequestInfo)base.Request.Items["info"];
Have DTO's share common interface
Another option for adding extra info to your Service is to have Request DTO's implement an interfaces, e.g:
public interface IHasInfo
{
MyRequestInfo Info { get; set; }
}
Which you could then populate in your Request Filter, e.g:
((MyRequestInfo)dto).Info = new MyRequestInfo { ... };
Access in Service like any other DTO property, e.g:
public object Any(Request request)
{
var info = request.Info;
}