I am trying to refactor my api into a minimal api. Previously I've been using ControllerBase.HttpContext to get the user like this:
var emial = HttpContext.User.FindFirstValue(ClaimTypes.Email);
The method that I want to use for my endpoint mapping should be something like this:
public static void MapSurveyEndpoints(this WebApplication app) {
app.MapPost("/api/Surveys", AddSurveysAsync);
}
public static async Task<Survey> AddSurveysAsync(ISurveyRepository repo, Survey survey) {
var email = ...; //get current user email
survey.UserEmail = email;
return await repo.AddSurveysAsync(survey);
}
What would be another approach for getting the user without using controller?
You can take the HttpContext as a parameter of your endpoint. ASP.NET Core will then provide that to you.
public static async Task<Survey> AddSurveysAsync(
HttpContext context,
ISurveyRepository repo,
Survey survey)
Minimal APIs have several parameter binding sources for handlers, including special types, like HttpContext as suggested in another answer, but if you need only the user info you can add just ClaimsPrincipal (which is one of the special types) parameter to your method:
app.MapGet("/...", (..., ClaimsPrincipal user) => user.Identity.Name);
Related
I've an app that will have multiples level of organization, and for each level, there will be rights(admin-reader-...).
I want to create(and maintain) a list of roles for each user, but it means that a lot of those roles name will be dynamic, like {id-of-the-organization]-admin.
Therefore, I cannot just do the usual Authorize:
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlAllPanelController : Controller
{
public IActionResult SetTime() =>
Content("Administrator || PowerUser");
[Authorize(Roles = "Administrator")]
public IActionResult ShutDown() =>
Content("Administrator only");
}
I would like to have something like
public class ControlAllPanelController : Controller
{
[Authorize]
public IActionResult SetTime(Guid organizationId) {
someService.Authorize(organizationId+"-SetTime");//Throw exception or return boolean
//... rest of my logic
}
}
Not sure how to achieve this? I've seen example of this with the IAuthorize service, but this was requiring to provide policies name, which I don't have for this case(Or maybe there is one by default but I don't know its name. `
I've seen that the ClaimsPrincipal has a IsInRole, but I'm not totally sure it get the latest information from Asp.Net Core Identity Framwork(from the user manager) (only what is stored inside the token?)?
You can use HttpContext to look at the claims in the JWT.
I have recently been working with authorizations in .NET API and this is what I done:
var identity = this.HttpContext.User.Identities.FirstOrDefault();
var role = identity.Claims.FirstOrDefault(x => x.Type == "role").Value;
if (role != "Admin")
{
return Unauthorized("You don't have to correct permissons to do this.");
}
So I'm getting the Identity details, then searching the claims for the role claim.
As a side note, Im using this in a controller inheriting from ControllerBase so I believe HttpContext is a property of this class so no need to inject it if you're using this. Else, you'd probably have to use it via DI, but should all work the same.
I'm exploring Minimal APIs in .Net 6, and trying to apply a custom Authorization Filter to the endpoint (via Attributes or Extensions).
But it seems to me, I am doing something wrong, or it's simply not designed to work in that way (and it's sad if so).
Couldn't find anything in the docs besides the default usage of [Authorize] attribute in Minimal APIs.
Here is the Filter
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//Checking tokens
}
And if I try to apply it at Controller level, it works fine
[CustomAuthorize]
public class CustomController : ControllerBase
{
//Necessary routing
}
But if I switch to Minimap APIs notation and try to use attributes
app.MapGet("/customEndpoint",
[CustomAuthorize] async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id));
or even an extension method
app.MapGet("/customEndpoint",
async ([FromServices] ICustomService customService, Guid id) =>
await customService.GetCustomStuff(id)).WithMetadata(new CustomAuthorizeAttribute());
It just doesn't work. The filter doesn't even being constructed.
What did I miss or did wrong?
Thx in advance
You can write a custom authorization filter for Minimal API in .NET 6.0
Here is how I tend to approach it - by using Policy-based authorization in ASP.NET Core
Step 1: Create a Requirement
A requirement implements IAuthorizationRequirement
public class AdminRoleRequirement : IAuthorizationRequirement
{
public AdminRoleRequirement(string role) => Role = role;
public string Role { get; set; }
}
Note: A requirement doesn't need to have data or properties.
Step 2: Create a Requirement Handler
A requirement handler implements AuthorizationHandler<T>
public class AdminRoleRequirementHandler : AuthorizationHandler<AdminRoleRequirement>
{
public AdminRoleRequirementHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
if (context.User.HasClaim(c => c.Value == requirement.Role))
{
context.Succeed(requirement);
}
else
{
_httpContextAccessor.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
_httpContextAccessor.HttpContext.Response.ContentType = "application/json";
await _httpContextAccessor.HttpContext.Response.WriteAsJsonAsync(new { StatusCode = StatusCodes.Status401Unauthorized, Message = "Unauthorized. Required admin role." });
await _httpContextAccessor.HttpContext.Response.CompleteAsync();
context.Fail();
}
}
private readonly IHttpContextAccessor _httpContextAccessor;
}
Note: HandleRequirementAsync method returns no value. The status of either success or failure is indicated by calling context.Succeed(IAuthorizationRequirement requirement) and passing the requirement that has been successfully validated or by calling context.Fail() to indicate AuthorizationHandlerContext.HasSucceeded will never return true, even if all requirements are met.
Step 3: Configure Your Policy in the Authorization Service
builder.Services.AddAuthorization(o =>
{
o.AddPolicy("AMIN", p => p.AddRequirements(new AdminRoleRequirement("AMIN")));
});
Step 4: Add Your Requirement Handler to DI
builder.Services.AddSingleton<IAuthorizationHandler, AdminRoleRequirementHandler>();
Step 5: Apply Policy to Endpoints
app.MapGet("/helloworld", () => "Hello World!").RequireAuthorization("AMIN");
I think you won't be able to inject action filter in minimal api, you can use 3 alternative approches.
Create a custom middleware and inject it in startup class, it would check every request and do the intended work as you filter is doing. You can put a check for the request path there if you only need to validate a specific controller/endpoint.
The second approach is you can inject httpcontext in minimal api like this, from that extract jwt token and validate that, if found not ok reject that request.
app.MapGet("/customEndpoint", async (HttpContext context, ICustomService service) =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.isNullOrEmpty(token) || <not a valid token>) return Results.Unauthorized();
// do some work
return Results.Ok(result);
});
as #Dai suggested, you can extract token in this way also
AuthenticationHeaderValue.TryParse(context.Request.Headers["Authorization"], out var parsed ) && parsed.Scheme == "BearerOrWhatever" ? parsed.Parameter : null
You can register the filter globally from startup.cs.
I am making a DLL to consume a REST API in aspnetcore.
Ideally, I would like it to be accessed this way:
API api = new API(clientInfo);
api.Module.Entity.Action(params);
But I am struggling to make that a reality. I can't make anything static because more than 1 session might be instanced at the same time. I can't pass the session around except by reference otherwise session state(cookies etc.) might change in the copy. Is there a design pattern I should be using?
public class API
{
private Session _session;
public API(ClientInfo clientInfo)
{
_session = new Session(clientInfo);
}
}
The session serves as middleware for the client, stores login data in case the client needs to repeat login, handles some errors/retries and exposes client methods.
public class Session
{
private Client _client;
private string _path;
public Session(ClientInfo clientInfo)
{
_client= new Client(clientInfo);
_path = clientInfo.Path;
}
public HttpResponseMessage Get(string name, string arguments = "")
{
return _client.Get(_path, name, arguments);
}
...
}
The client actually performs the calls.
public class Client
{
public HttpResponseMessage Get(string path, string endpointName, string arguments)
{
return GetClient().GetAsync(path + endpointName + arguments).Result;
}
private HttpClient GetClient(){...}
...
}
Personally, I just build a simple client for my APIs, with methods corresponding to the endpoints the API has:
public class FooClient
{
private readonly HttpClient _httpClient;
public FooClient(HttpClient httpClient)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
public async Task<GetFooResult> Get(int id)
{
...
}
// etc
}
The HttpClient dependency is provided by registering a typed client in Startup.cs:
services.AddHttpClient<FooClient>(c =>
{
// configure client
});
And I add an IServiceCollection extension to encapsulate this and any other setup logic:
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddFooClient(this IServiceCollection services, string uri)
{
...
}
}
Then, in my Startup, I can simply do something like:
services.AddFooClient(Configuration.GetValue<string>("FooUri"));
This is extremely helpful for setting up automatic error handling, retry policies, etc. via Polly, as you can then set up all that configuration just once in the extension.
Now, getting to your issue of persisting things like auth tokens, you have a few possibilities. I tend to prefer persisting auth tokens as claims, in which case you can simply retrieve the claim and pass it into methods on your client that need it:
var foo = await _fooClient.Get(fooId, User.FindFirstValue("FooAuthToken"));
If you handle things that way, you can bind your client in any scope, including singleton.
An alternative approach would be to actually persist the auth token in your client, but this has to be done with care. You should definitely avoid using singleton scope, unless you're employing something like a ConcurrentDictionary and even then, ensuring that the right token is always used could be a bit gnarly.
Assuming you're using a request scope, you can store the token directly as an ivar or something, but you'd still need to persist it some place else beyond that, or you'd still need to re-auth for each request. If you were to store it in the session, for example, then you could do something like:
services.AddScoped<FooClient>(p =>
{
var httpClientFactory = p.GetRequiredService<IHttpClientFactory>();
var httpContextAccessor = p.GetRequiredService<IHttpContextAccessor>();
var httpClient = httpClientFactory.Create("ClientName");
var session = httpContextAccessor.HttpContext.Session;
var client = new FooClient(httpClient);
client.SetAuthToken(session["FooAuthToken"]);
});
However, even then, I'd still say it's better to pass the auth token into the method than do any of this. It's more explicit about which actions require auth versus those that do not, and you always know exactly what's coming from where.
One of your biggest problems will be the reuse of the HttpClient. This is a known problem for "pre-Core" days. Luckily, its been addressed and as of Net Core 2.1 we now have an HttpClientFactory which allows you to spin up as manage HttpClients as you need and they're handled for you as part of the framework.
https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore
With this in mind, theres nothing stopping you from using DI to inject an IHttpClientFactory which will provide you with the necessary access to the pipeline you need. Other than that, its entirely up to you how you design the code which "manages" your access to the REST resources. Maybe some sort of Repository Pattern? (Purely guess work really without knowing your architecture etc)
I'm confused on how the OWIN CreatePerOwinContext method is to be used. As far as I can see it's a poor mans DI mechanism. Yet, I fail to see how to use it.
We can register a type/implementation at the Startup sequence like:
app.CreatePerOwinContext<IUserService>(() => {
return new UserService() as IUserService;
});
Then how do we resolve to that later on. Documentation says it can be retrieved via Get method. But Get<T> expects a string parameter, which is the key to that entry in the Enviornment IDictionary? How can I know the key in this case?
IUserService userService = context.Get<IUserService>(???);
You can use typeof to get the key parameter:
HttpContext.GetOwinContext().Get<ApplicationDbContext>(typeof(ApplicationDbContext).ToString());
Also, Microsoft.AspNet.Identity.Owin assembly contains the parameterless version of Get<T>() method, so you can use it if you already have ASP.NET Identity in your project.
I have a more correct answer after running into this myself, trying to implement the code within this stackoverflow answer: https://stackoverflow.com/a/31918218
So given this initialization code within the conventional Configure method:
static void Configuration(IAppBuilder app)
{
//https://stackoverflow.com/a/31918218
app.CreatePerOwinContext<AppBuilderProvider>(() => new AppBuilderProvider(app));
ConfigureAuth(app); //note implementation for this is typically in separate partial class file ~/App_Start/Startup.Auth.cs
}
One can retrieve the instance created by this code:
public ActionResult SomeAction()
{
//https://stackoverflow.com/a/31918218
var app = HttpContext.GetOwinContext().Get<AppBuilderProvider>("AspNet.Identity.Owin:" + typeof(AppBuilderProvider).AssemblyQualifiedName).Get();
var protector = Microsoft.Owin.Security.DataProtection.AppBuilderExtensions.CreateDataProtector(app, typeof(Microsoft.Owin.Security.OAuth.OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1");
var tdf = new Microsoft.Owin.Security.DataHandler.TicketDataFormat(protector);
var ticket = new AuthenticationTicket(ci, null);
var accessToken = tdf.Protect(ticket);
//you now have an access token that can be used.
}
I have implemented a HMAC authentication filter (as per this article) in a Web API 2 project. The article uses static keys for demo purposes so I have modified the filter to look up the Private API Key from a database using an 'AppId' Guid. This works nicely as I'm able to load the appropriate account for that AppId entry. But I would like to know if it's possible to access the "Account" object created in the authentication filter class in my controller.
This is how I declared the object:
public class HMACAuthenticationAttribute : Attribute, IAuthenticationFilter
{
private static Dictionary<string, string> allowedApps = new Dictionary<string, string>();
public DummyAccount account = new DummyAccount();
}
And later down in the code:
// Load account and its private API Key
account = accountService.GetByAppId(Guid.Parse(appId), session);
if (account == null)
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
// If we find the account, we add to allowedApps the AppId/PrivateKey pair
allowedApps.Add(account.AppId.ToString(), account.ApiKey);
Now in my WebApi Controller, the code looks like this:
[HMACAuthentication]
[HttpPost]
public RatingDto Post(SearchTrackDto searchedTrack)
{
// Access 'account' object here?
}
My expectation is that I can somehow access the account object directly so I don't have to parse the request again and make a second database query. Would storing the object in the Request.Context be the way to go? What alternative is there?
You could try implementing a cache. The AuthenticationFilter is supposed to validate each Request, as the Context lifetime scope is as long as the requests dies.