I am doing AzureAD implict flow for authenticating on a ASP.NET Core (2.2) api. For a specific reference to a library the application is in .net 4.8 but the api is hosted inside asp.net core.
The API makes a service to service call using the TPL dataflow blocks. The access token received when the UI(react) makes the request has got the roles and scopes and is stored inside HttpContext for downstream api calls (OnBehalfOf flow).
The HttpContextAccessor.HttpContext is null inside the pipeline. Outside of the pipeline I have access to the HttpContext. It looks to be really ugly to pass the context into every method call as a parameter.
Is there any other way to do this? I know I can use Client Credential flow, where I could configure the AD and use daemon app and get a token. But if I am stuck doing an OnBehalfOf flow, what could be the solution.
public class AuthenticationHandler : AuthenticationHandler<JwtBearerOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey(HeaderNames.Authorization))
{
return Task.FromResult(AuthenticateResult.Fail("Header Not Found."));
}
var requestHeader = Request.Headers["Authorization"];
var token = requestHeader.FirstOrDefault()?.Split(' ').Last();
Request.HttpContext.Items.Add("tokenfordownstream", token); // retrieve from context for api to api calls
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
Microsoft.Identity.Web could not be installed because of a number of package downgrade. I think it is conflicting with binaries in Microsoft.Identity.Client
using Microsoft.Identity.Client;
public class RiskPipelineFactory : IRiskPipelineFactory
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RiskPipelineFactory(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor; //httpContext is NOT null
}
public IRiskPipeline Create()
{
var preLoaderAction = new ActionBlock<(pricingExercise, risks)>(LoadStep, loadOptions);
return new RiskSlicePipeline(preLoaderAction);
//I don't want to pass the httpContext here, because there are also other blocks that
//gets stitched togther and many method calls, so it will be quite messy
private async Task LoadStep((PricingExercise pricingExercise, Risks risks) input)
{
//HttpContext is null here, if _httpContextAccessor is not passed as a param
//do the other api call here for the sake of simplicity, but retrieving the access token will fail
}
}
}
Related
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'm trying to make use of IHttpClientFactory for the first time in a .NET Core 3.1 MVC project. I am using HttpClient to make calls to the Microsoft Graph API.
Whenever I need to make a call to the API I need to first check that the authentication token the application has is valid (i.e. hasn't expired) and, if not, use the refresh token to obtain a new authentication token.
Before learning of IHttpClientFactory I was initialising an instance of HttpClient for my application
and then whenever I needed to make an API call I'd first call my own PrepareHttpClient() function, so it'd go like this:
public class MyController : Controller
{
private static HttpClient httpClient = new HttpClient();
private readonly IOAuthService _oAuthService;
public MyController(IOAuthService oAuthService)
{
_oAuthService = oAuthService;
}
public async Task<IActionResult> Index()
{
var url = "http://whatever";
await PrepareHttpClient();
var response = await httpClient.GetAsync(url);
// Other stuff
}
private async Task PrepareHttpClient()
{
httpClient.DefaultRequestHeaders.Clear();
string bearerToken = await _oAuthService.GetOAuthBearerToken();
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}");
}
}
The GetOAuthBearerToken function is in a service which, as you can see, is injected into the controller and it basically does the dance of checking if the current token is expired and obtaining a new one if required, and it returns the valid token as a string. I don't think the full code of that function is necessary here but I can add it if required.
After learning about IHttpClientFactory and the fact that you can give an HttpClient a name and configure it in Startup.cs I thought "Great, I'll just call the GetOAuthBearerToken function when configuring the client". Something like
services.AddHttpClient("OneDrive", async(c) => {
string bearerToken = await IOAuthService.GetOAuthBearerToken();
c.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}");
});
However, there are a number of problems with this:
Injecting a service in to the Startup class seems to be a no-go, which makes sense, so I can't inject my IOAuthService as I do in the controller.
The GetOAuthBearerToken itself uses HttpClient to obtain updated API tokens, and using DI to inject an IHttpClientFactory into a function which is itself injected into Startup sounds like... I'm losing my mind.
Ultimately I realised that none of that mattered because I need to check and refresh the API token every time I make a call, not just when the client is instantiated when the application starts. So it seems to me that the most elegant solution would be to override the CreateClient method in IHttpClientFactory, which I call right before making every API call, so that it could call my GetOAuthBearerToken function and then I'd know that every time I called CreateClient the HttpClient was good to go, with a valid bearer token in the header.
My question is (finally!), how would I go about overriding CreateClient in this way? Assuming that's the best way to achieve my aim.
Sorry, if someone could make this a comment.
The first thing I notice is you forget to clear the headers and add the new header for each request, for the instance you are registering in your startup.
Edit:
I'll just go ahead and answer it. Sorry I don't have any sort of c# compiler or intellisense.
public class MyController : Controller
{
private static IHttpClientFactory httpClientFactory = new IHttpClientFactory();
private readonly IOAuthService _oAuthService;
public MyController(IOAuthService oAuthService,IHttpClientFactory myfactory)
{
_oAuthService = oAuthService;
httpClientFactory = myfactory;
}
public async Task<IActionResult> Index()
{
var url = "http://whatever";
HttpClient myclient = httpClientFactory.CreateClient("MyNamedClient");
var response = await HttpClient.GetAsync(url);
// Other stuff
}
}
I have a project written in ASP.NET Core 3.1.
I need to set data to Session in Singleton service:
_session.SetString("some key", "some value");
I injected the session object from DI:
public OperatorService(ILogger<OperatorService> logger,
ISession session,
IOptions<AppSettings> options)
{
this._session = session;
this._logger = logger;
this._appSettings = options.Value;
}
I calls the my method as below:
public void ChangeOperatorStatus(StatusChangeRequest request)
{
try
{
_session.SetString(request.Key, request.Value);
}
catch (Exception ex)
{
_logger.LogInformation($"Exception while changing status: {ex}");
}
}
but I get the exception below :
IFeatureCollection has been disposed.\r\nObject name: 'Collection'.
and I added some code to Startup.cs's ConfigureServices method:
services.AddHttpContextAccessor();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(20);
options.Cookie.HttpOnly = true;
})
.AddDistributedMemoryCache();
And I added app.UseSession(); to the Configure method of Startup.cs.
I trid services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); and I get the session from httpContextAccessor.HttpContext.Session but I get the same error.
Please help me, thank you.
An HttpContext is the context of a single request. It provides access to the request, response properties etc of that single request. You can't cache it, it becomes invalid once that request ends.
Session is another transient thing - it lives only as long as a single user session. There's at least one session for every user of a web app. Caching one of those sessions in a singleton guarantees that
The reference will become invalid after a while, when the session expires and
The singleton will use only that user's values, ignoring everyone else's. This is a bug in itself, and a great way to hack into an application.
If an administrator logs in, the Session object may apply the admin's settings alive to everyone for the next 20, 30 or 60 minutes.
That's why using a Session makes sense for per-request middleware, not Singleton services.
Correct usage of HttpContext
The Session can only be reached through the request's context, so getting the correct session means getting the correct HttpContext. The correct way to do this is explained in David Fowler's ASP.NET Core Guidance :
❌ BAD This example stores the HttpContext in a field then attempts to use it later.
private readonly HttpContext _context;
public MyType(IHttpContextAccessor accessor)
{
_context = accessor.HttpContext;
}
public void CheckAdmin()
{
if (!_context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
✅ GOOD This example stores the IHttpContextAccesor itself in a field and uses the HttpContext field at the correct time (checking for null).
private readonly IHttpContextAccessor _accessor;
public MyType(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public void CheckAdmin()
{
var context = _accessor.HttpContext;
if (context != null && !context.User.IsInRole("admin"))
{
throw new UnauthorizedAccessException("The current user isn't an admin");
}
}
Use a Scoped service instead
Since a Singleton can't know what session to use. One option is to simply convert that service to a Scoped service. In ASP.NET Core, a request defines a scope. That's how controller actions and pipeline middleware get access to the correct HttpContext for each request.
Assuming the service is used by an action or middleware, perhaps the only change needed is to replace AddSingleton<ThatService> with AddScoped<ThatService>
Turning the tables, or Inversion of Control
Another option is for callers of that singleton should provide the session to it. Instead of using a cached session eg :
public void SetStatus(string status)
{
_session.SetString(SessionKeys.UserStatus, "some value");
}
Ask for the session or HttpContext as a parameter :
public void SetStatus(string status,ISession session)
{
session.SetString(SessionKeys.UserStatus, "some value");
}
And have callers pass the correct session to it
It took me a while to get this fixed.
In my case, it was a 3.1 aspnetcore and it didn't worked until I turn the container function from
public async void OnPost
to
public async Task<IActionResult> OnPost
Looks like the HttpContext was disposed before it was used...
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)
This question is essentially the same as the one here, but, for asp.net core while using the asp.net core cookie middleware.
Is accessing query string/request body data possible on validation, and if it is, would you encourage the idea? It seems that according to this that it is very much possible, however, are the same rules in play from big boy asp.net (such as you are only to read the request data once in a given requests lifetime)?
Example: I'm creating an app where people have one account, but, are members of different teams. They can perform many different actions in the app, and, they can perform that action while in the "context" of one team or another that they are a member of. So, I have a teamId integer being passed in requests made to the server. I'd like to pull claims off the ClaimsPrincipal verifying that they really are a member of that team in the authorization portion of the pipeline.
As you said it is possible to access request's data on OnValidatePrincipal event. So, you can write something like this:
OnValidatePrincipal = async (context) =>
{
if (context.Request.Path.Value.StartsWith("/teams/"))
{
var teamId = // get team id from Path;
if (user is not team member)
{
context.Response.StatusCode = 403;
}
}
}
However, i think your requirement is related Authorization rather than Authentication. I would use Policy-Based Authorization to handle the requirement. Example policy should be like this:
Requirement and Handler:
public class TeamMemberHandler: AuthorizationHandler<TeamMemberRequirement>
{
private readonly IActionContextAccessor _accessor; // for getting teamId from RouteData
public TeamMemberHandler(IActionContextAccessor accessor)
{
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TeamMemberRequirement requirement)
{
var teamId = // get teamId with using _accessor
if (user is not member of team(by teamId))
{
context.Fail();
}
return Task.FromResult(0);
}
}
public class TeamMemberRequirement : IAuthorizationRequirement
{
}
Configure Services:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("TeamMember",
policy => policy.Requirements.Add(new TeamMemberRequirement()));
});
services.AddSingleton<IAuthorizationHandler, TeamMemberHandler>();
}
Finally use it on top of controller(or if you want, you can add filter globally)
Authorize[(Policy = "TeamMember")]
public class TeamHomeController : Controller
{
// Authorize[(Policy = "AnotherPolicy")]
public IActionResult Index(){}
}