ASP.NET Core Middleware Passing Parameters to Controllers - c#

I am using ASP.NET Core Web API, where I have Multiple independent web api projects. Before executing any of the controllers' actions, I have to check if the the logged in user is already impersonating other user (which i can get from DB) and can pass the impersonated user Id to the actions.
Since this is a piece of code that gonna be reused, I thought I can use a middleware so:
I can get the initial user login from request header
Get the impesonated User Id if any
Inject that ID in the request pipeline to make it available to the api being called
public class GetImpersonatorMiddleware
{
private readonly RequestDelegate _next;
private IImpersonatorRepo _repo { get; set; }
public GetImpersonatorMiddleware(RequestDelegate next, IImpersonatorRepo imperRepo)
{
_next = next;
_repo = imperRepo;
}
public async Task Invoke(HttpContext context)
{
//get user id from identity Token
var userId = 1;
int impersonatedUserID = _repo.GetImpesonator(userId);
//how to pass the impersonatedUserID so it can be picked up from controllers
if (impersonatedUserID > 0 )
context.Request.Headers.Add("impers_id", impersonatedUserID.ToString());
await _next.Invoke(context);
}
}
I found this Question, but that didn't address what I am looking for.
How can I pass a parameter and make it available in the request pipeline? Is it Ok to pass it in the header or there is more elegant way to do this?

You can use HttpContext.Items to pass arbitrary values inside the pipeline:
context.Items["some"] = "value";

A better solution would be to use a scoped service. Take a look at this: Per-request middleware dependencies
Your code should look like:
public class MyMiddleware
{
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, IImpersonatorRepo imperRepo)
{
imperRepo.MyProperty = 1000;
await _next(httpContext);
}
}
And then register your ImpersonatorRepo as:
services.AddScoped<IImpersonatorRepo, ImpersonatorRepo>()

Related

Where to store license information that I will need at multiple levels?

I'm developing .Net 6 API.
My project includes controllers, services and repositories (using dependency injection).
I also added a permission check via middleware:
Program.cs
app.UseMiddleware<AuthMiddleware>();
AuthMiddleware.cs
public class AuthMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<EcmAuthMiddleware> _logger;
private readonly IConfiguration _config;
public AuthMiddleware(RequestDelegate next,
ILogger<AuthMiddleware> logger, IConfiguration config)
{
_next = next;
_logger = logger;
_config = config;
}
public async Task Invoke(HttpContext context, IUserApiService
userApiService)
{
...
context.Items["Instance"] = instance;
await _next(context);
}
}
From here I get the customer (and database) to run the APIs on.
Now I need to get some license information (via external API) from the newly obtained client and store it somewhere.
I tried invoking the call from the controller but would have to repeat it for almost all controllers. So I thought about transferring the call to middleware.
From the call I will have various information that I would like to store for use by the underlying levels: controllers, services and repositories. I'd rather not use the session or coookie.
Can I use only httpcontext or are there other solutions?
context.Items["LicenseInfo"] = licenseInfo;
This information is valid only for the call of an api then it should not be stored (eg Application).
EDIT:
GetLicenseInfo() must contains an external call:
string result = await _userApiService.GetUserApiResponseAsString("users/token", HttpMethod.Get, applicationId, token);
Can I use only httpcontext or are there other solutions?
There's nothing wrong with using HttpContext.Items for this. It's exactly what HttpContext.Items is for: attaching contextual data to an HTTP request. With this kind of "dictionary of objects" API, I do like to wrap my own APIs around it for type safety and simplicity:
public static class HttpContextLicenseInfoExtensions
{
public static void SetLicenceInfo(this HttpContext context, LicenseInfo licenseInfo) =>
context.Items[key] = licenseInfo;
public static LicenseInfo? TryGetLicenseInfo(this HttpContext context) =>
context.Items[key] as LicenseInfo;
public static LicenseInfo GetLicenseInfo(this HttpContext context) =>
context.TryGetLicenseInfo() ?? throw new InvalidOperationException("No license info.");
private static readonly string key = Guid.NewGuid().ToString("N");
}
// Example middleware
app.Use(async (context, next) =>
{
context.SetLicenseInfo(licenseInfo);
await next.Invoke();
});
// Example usage
var licenseInfo = HttpContext.GetLicenseInfo();
But if you really want to avoid HttpContext.Items, you can use AsyncLocal<T>. You just want to structure the API so that you set the value for a specific scope (I like to return IDisposable to un-set the value), and then you usually inject an "accessor" to read the current value. Something like this should work (using Disposable from my disposables library):
public static class AsyncLocalLicenseInfo
{
public static IDisposable Set(LicenseInfo licenseInfo)
{
var originalValue = local.Value;
local.Value = licenseInfo;
return new Disposable(() => local.Value = originalValue);
}
public static LicenseInfo? TryGet() => local.Value;
public static LicenseInfo LicenseInfo => TryGet() ?? throw new InvalidOperationException("No license info.");
private static readonly AsyncLocal<LicenseInfo> local = new();
}
// Example middleware
app.Use(async (context, next) =>
{
using var localValue = AsyncLocalLicenseInfo.Set(licenseInfo);
await next.Invoke();
});
// Example usage
var licenseInfo = AsyncLocalLicenseInfo.LicenseInfo;
If you don't like the static API, you can hide it behind an "accessor":
// Inject this into downstream types
public interface ILicenseInfoAccessor
{
LicenseInfo LicenseInfo { get; }
}
public sealed class LicenseInfoAccessor : ILicenseInfoAccessor
{
public LicenseInfo LicenseInfo => AsyncLocalLicenseInfo.LicenseInfo;
}
// Example usage
var licenseInfo = licenseInfoAccessor.LicenseInfo;
This is only an alternative to MiddleWare which might be a better option, might not, it depends on a lot of things (as in most software dev questions).
This could be the factory:
public class LicenseInfoFactory
{
public LicenseInfoFactory(IHttpContextAccessor context)
{
_context = context;
}
private readonly IHttpContextAccessor _context;
public LicenseInfo Build()
{
_context...
// api call to get/build/return LicenseInfo
}
}
And then in your startup:
services.AddHttpContextAccessor();
services.AddSingleton<LicenseInfoFactory>();
services.AddScoped<LicenseInfo>(provider => {
var factory = provider.GetRequiredService<LicenseInfoFactory>();
return factory.Build();
});
Then just inject LicenseInfo in your controllers/repositories constructors etc:
public MyController(LicenseInfo licenseInfo)
This should only make one api call per scoped request. (this is from memory, syntax might not be exact)

How to check/redirect in every method of any controller in ASP NET Core 3.1?

I'm looking for a way to do this in every Method of every Controller, even for those that doesn't return an IActionResult (I'll talking about this forward):
Get User.Claim to get the user info logged into the site.
Check if the user is blocked in the database (I've my own repositories working already)
Redirect a user to a Page that shows "you are blocked" like the workflow of Exceptions does.
Considerations and tries that I already made:
Can't get this from a Claim in order to work it with ViewStart because I need the block works in the same moment the database were updated. The Claim should the user sign out and sign in.
Can't do this trough a controller that returns a boolean with Json in
every page because the user could stop the redirect
I've several methods that returns partialviews, objects, lists, anything but IActionResult. I don't care if that doesn't work for two reasons:
It should be triggered by the main view that calls those through ajax.
The user is blocked anyway so I don't care if it see the page broken.
My security workflow is like this:
[HttpPost]
[Authorize(Policy = "Users")]
[Authorize(AuthenticationSchemes = "MyAppScheme")]
public IActionResult GetRep() {
//Do things
}
Right now I'm trying do a middleware but I'm new in NET Core at so deep level, so can't compile neither. Also working trying to call my userRepository from the ViewStart
The best way is using a middleware. Here you have an example:
internal class UserMiddleware
{
private readonly RequestDelegate next;
private readonly IUserRepository userRepository;
public UserMiddleware(RequestDelegate next, IUserRepository userRepository)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
this.userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
}
public async Task Invoke(HttpContext httpContext)
{
Claim clientId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier);
bool isBlocked = await this.userRepository.CheckUser(clientId);
if (isBlocked)
{
await httpContext.Response.WriteAsync("You are blocked.");
return;
}
await this.next(httpContext);
}
}
Then in your startup method you should call it before mapping your controllers:
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other stuff...
app.UseMiddleware<UserMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Does knowing the Identity ID compromise security?

I am looking at a Unit Test project, which contains the following code:
public const string IDENTITY_ID = "9e326389-1ae6-4672-9dc4-7868ab7b7a09";
private readonly RequestDelegate _next;
public AutoAuthorizeMiddleware(RequestDelegate rd)
{
_next = rd;
}
public async Task Invoke(HttpContext httpContext)
{
var identity = new ClaimsIdentity("cookies");
identity.AddClaim(new Claim("sub", IDENTITY_ID));
identity.AddClaim(new Claim("unique_name", IDENTITY_ID));
httpContext.User.AddIdentity(identity);
await _next.Invoke(httpContext);
}
Please note that I have generated a random Guid for the question.
It appears that theunit test invokes the web api (inside the Invoke method) and is able to access a method, which has an Authorise attribute.
Does forumating an identity like this mean the user is authorised?
I am trying to learn more about how Identity Server works and hence the reason for the question.

Get reference to requested Controller and Action in custom middleware in ASP.NET Core

I'm developing a custom middleware for authenticating clients that invokes an API.
I use an attribute to define if an Action requires authentication, but I can't figure out how to get a reference to the requested Controller and Action inside the Invoke method.
Below is my code so far
AuthenticateClient.cs:
public class AuthenticateClient
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly GenericUnitOfWork _worker;
public AuthenticateClient(RequestDelegate next, ApiDbContext db, IHttpContextAccessor httpContext, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Utility.LCLog.Settings> settings)
{
_next = next;
_logger = loggerFactory.CreateLogger(settings.Value.ApplicationName);
_worker = new GenericUnitOfWork(new AppHelper(httpContext, db, env));
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("ClientAuth"))
{
_logger.LogWarning("ClientAuth missing in request", new string[] { "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 400;
await context.Response.WriteAsync("ClientAuth missing from request header values");
return;
}
else
{
string[] tmp = context.Request.Headers["ClientAuth"].ToString().Split("/");
if (tmp.Length != 2)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("The format of the ClientAuth value is wrong");
return;
}
else
{
Client client;
string key, pass;
key = tmp[0];
pass = tmp[1];
client = await _worker.GetRepo<Client>().SingleOrDefault(clnt => clnt.Active && clnt.Key.Equals(key) && clnt.Password.Equals(pass));
if (client == null)
{
_logger.LogWarning("Client authentication failed", new string[] { "Key: " + key, "Password: " + pass, "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Authentication failed");
return;
}
}
}
await _next.Invoke(context);
}
}
ClientAuthenticationAttribute.cs:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ClientAuthenticationAttribute : Attribute
{
private readonly bool _authRequired;
public ClientAuthenticationAttribute(bool authRequired = true)
{
_authRequired = authRequired;
}
public bool AuthRequired { get { return _authRequired; } }
}
I'd recommend you to split your logic for authentication and authorization and keep them in different places.
To recap from here:
Authentication is the process of verifying who you are.
Authorization is the process of verifying that, given that we know who you are, you have access to the specific resource.
What you're currently trying to do, is to both authenticate and authorize your user in the middleware component. Although you could probably get it to work by moving all such logic into filters which you register with the api framework (be it ASP.NET Core MVC, Web API 2 or something else), that would mean that none of your other middleware components have access to the user data (which, I'm guessing, is one of the reasons you chose to implement it in a middleware in the first place).
Given your new knowledge of the separation of authentication and authorization, a possible solution would be to do the following:
Middleware for authentication only
In your middleware, concern yourself only with authentication, and leave authorization up to components later in the pipeline. In practice, this means that your middleware should do the following:
Look for user tokens, cookies or whatever you use for the users to authenticate their request
If not present, treat the request as anonymous, and call the next pipeline component without attaching a user to the request context.
If a valid token is present, resolve the user data from it (e.g. parse the user's claims from a JWT, look up roles in a database, etc...) and store it on the request context. I've found it useful both to create an IPrincipal and set context.Request.User to it, as well as adding information to the context dictionary directly.
With the user registered in the request context, call the next pipeline component.
Authorization assuming an authenticated user
You can now re-write your authorization logic to assume that there's already an authenticated user registered on the request context.
In an ASP.NET Web API 2 application, you'd implement a custom filter attribute inheriting from AuthorizationFilterAttribute, to make sure it runs first of the filters. In my current application, for example, we have the following attribute to authorize that a user has a specific claim. Note that it doesn't do any work to figure out who the user is; if a user is not attached to the context, the response is simply Unauthorized. You could be more sophisticated here, and treat anonymous requests differently from authenticated requests for users who lack access, and, for example, redirect anonymous requests to the login form, while redirecting users lacking access to an error page stating as much.
[AttributeUsage(validOn: AttributeTargets.Method)]
public class AuthorizeClaimsFilterAttribute : AuthorizationFilterAttribute
{
public AuthorizeClaimsFilterAttribute(string claimType, string claimValue)
{
ClaimType = claimType;
ClaimValue = claimValue;
}
public string ClaimType { get; }
public string ClaimValue { get; }
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!(actionContext.RequestContext.Principal is ClaimsPrincipal principal)
|| !principal.HasClaim(x => x.Type == ClaimType && x.Value == ClaimValue))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
To use it, we just decorate the action method with it:
[AuthorizeClaimsFilter("urn:ourapp:claims:admin", true)]
public IHttpActionResults OnlyAdminsCanAccess() { /* ... */ }

Scoped service in controller is different from the service called in the middleware

I have a custom Authentication Middelware which uses my custom service injected in the constructor.
In MyAuthenticationHandler I am calling a method of MyService which sets a property value.
_myService.SetCompany(company);
company is loaded in the authentication handler and is not null. However when I try to access the value from the controller I find that MyService has been reinitialized.
This is how it's set in Startup.cs
services.AddScoped<IMyService, MyFactory>();
Middleware is only initialized once, when you register. You need to resolve your dependency in the Invoke method.
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var service = context.RequestServices.GetService<IMyService>();
service.SetCompany("My Company");
await _next.Invoke(context);
}
}
Now the service is properly resolved per request, rather than per application life time.
Edit:
i.e. in order to have your middleware be called after the authorization middelware is called you'd do something like this in your Configure(IAppBuilder app) method:
app.UseCookieAuthentication(options => { ... });
app.UseJwtBearerAuthentication(options => { ... });
app.UseMiddleware<MyMiddleware>(options => { ... });
Then on a request, first the cookie middleware will be called. If it can handle the scheme and it fails, then following middlewares won't be executed. If it can't handle the scheme, next one will be called (jwt bearer). If that passes, the next middleware (MyMiddleware) will be called.
In other words, when your MyMiddleware.Invoke() method is being called, the user has been authenticated.
You can inject the service directly in the Invoke signature.
From the official doc here:
If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by dependency injection.
So in your case:
public async Task Invoke(HttpContext context, IMyService service)
{
service.SetCompany("My Company");
await _next.Invoke(context);
}
will work.

Categories

Resources