Access Session Values in a AddHttpMessageHandler Method - c#

I am trying to create a generic way to add my access token to my outgoing calls in a Razor application. I followed the documentation to add an DelegatingHandler via AddHttpMessageHandler:
services.AddHttpClient("MyName").AddHttpMessageHandler<AddAuthorizationHeaderHandler>();
My handler is called correctly, but I can't find a way to get at the session value that holds the access token (so I can add it as a header).
ASP.Net Core is storing this value in the cookie. In the "code behind" of my page, I can get at the value via HttpContext (a public member of the PageModel class). Like this:
var accessToken = await HttpContext.GetTokenAsync("access_token");
The problem I have is that I can't find a way to get access to this HttpContext in my DelegatingHandler.
How can I get at the session value of access_token in my DelegatingHandler?

Inject IHtpContextAccessor into the delegating handler
private readonly IHttpContextAccessor accessor;
public AddAuthorizationHeaderHandler(IHttpContextAccessor accessor) {
this.accessor = accessor
}
So that you can have access to the context
AddAuthorizationHeaderHandler.SendAsync
//...
var accessToken = await accessor.HttpContext.GetTokenAsync("access_token");
//...
Make sure to register it with the service collection
services.AddHttpContextAccessor();

Related

Implementing custom Authorization logic for Web API on ASP.NET Core 6

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.

IHttpContextAccessor contains empty User.Identity, when used outside of the controller

I am writing an app ASP.Net Core (2.2) MVC. I need to filter some the data inside the DbContext by value of certain claims of the Logged in user. I inject IHttpContextAccessor, but when I try to access HttpContext.User.Identity - all properties are null and all claims are empty.
This is how I am trying to achieve that
I wire up IHttpContextAccessor. I use a standard method like that:
public void ConfigureServices(IServiceCollection services){
services.AddHttpContextAccessor();
...
}
Then I build a custom Provider to extract claims from the User:
public class GetClaimsFromUser : IGetClaimsProvider
{
public string UserId {get; private set;}
public GetClaimsFromUser(IHttpContextAccessor accessor)
{
UserId = accessor.HttpContext?.User.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Name)?.Value;
}
}
Then I also inject it inside ConfigureServices method:
public void ConfigureServices(IServiceCollection services){
...
services.AddScoped<IGetClaimsProvider, GetClaimsFromUser>();
...
}
Afterwards I injected it inside the ApplicationDbContext and try to set the private _userId field inside the constructor:
public class ExpenseManagerDbContext: IdentityDbContext<ApplicationUser>
{
private string _userId;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IGetClaimsProvider claimsProvider) : base(options)
{
_userId = claimsProvider.UserId;
...
}
...
}
And exactly here it is empty. When I access the HttpContext inside the controller, the User.Identity is not empty and everything is fine. However, when I need to access it outside the controller, it is empty.
Thanks for any help!!!
The full code can be found here:
https://github.com/dudelis/expense-manager/blob/master/ExpenseManager.DataAccess/Concrete/EntityFramework/ExpenseManagerDbContext.cs?
You are attempting to access the user in ExpenseManagerDbContext which is the application’s IdentityDbContext. As such, it itself is a dependency of the authentication system and will get resolved when the framework performs the authentication.
So the flow is somewhat like this:
Request comes in.
Authentication middleware runs to authenticate the user.
UserManager resolves ExpenseManagerDbContext.
ExpenseManagerDbContext resolves IGetClaimsProvider.
GetClaimsProvider resolves the HttpContext and attempts to access the user’s claims.
Authentication middleware performs the authentication and sets HttpContext.User with the result.
If you look at steps 5 and 6, you will see that the HttpContext is accessed before the authentication middleware is able to actually authenticate the user and update the user object on the context. And since the authentication middleware always runs at the beginning of a request, this will always be the case.
I would recommend you to rethink your ExpenseManagerDbContext since it probably shouldn’t depend on the currently signed-in user. It should be independent of that. If you have logic there that depends on the user id, then it should probably be a separate service.
Solved!
The problem was in the sharing of the same DbContext for IdentityDbContext and ApplicationDataDbContext.
In my controller I had the following code:
[Authorize]
public class AccountController : Controller
{
[HttpGet]
public IActionResult Index()
{
var accounts = _accountService.GetAll();
var models = _mapper.Map<List<AccountDto>>(accounts);
return View(models);
}
}
And when I tried to call the controller from the browser, the app initialized DbContext first time due to [Authorize] attribute. And this was done without any HttpContext. So when the application made a call to the DbContext in '_accountService.GetAll()', the DbContext was already instantiated and the Constructor method was not called, therefore, all my private fields remained empty!
So I created a second DbContext class only for authentication/authorization purposes.
public class ApplicationDbAuthContext : IdentityDbContext
{
public ApplicationDbAuthContext(DbContextOptions<ApplicationDbAuthContext> options) : base(options)
{
}
}
Due to this, during the request inside the controller the correct DbContext was instantiated when I made a call and it contained the HttpContext.
I will update my code in the repo to show the changes.
Meanwhile, thanks for all the answers.

Using static variables in C# Web Application - Accessing Session variables in class properties

Forgive me for my lack of coding knowledge as well as ability to ask the right question.
I'm rather new to this ASP.Net Web Application thing (Core), yet I still wondered..
In my current application, I have a class that has a property in which it gets it from a static variable, set when a user requests a controller. So the flow is: User sends a request with a variable in body, if not specified in body, the StaticClass.StaticProperty (example) is then set to the variable the user specified in the body (or default = 0), data is returned based upon the variable.
Yet I wondered, since there is no thread guarantee on this variable, whether or not this could be changed or messed up when the web application gets 50,000 requests at once?
I looked into sessions and tried the following:
service.AddSession(); //Not sure this even does anything?
HttpContext.Session.SetString //Setting this works in the controller, but I cant access it elsewhere by GetString
System.Web.HttpContext.Current.Session["test"] // Cant even access System.Web.Httpcontext, doesn't seem to exist.
HttpContext.Current //doesn't exist either
Session["test"] //doesn't exist either
Can I send a session over somewhere? I'm pretty lost.
Not sure if any of this made sense, I'll try to elaborate if needed.
Thank you in advance.
EDIT: Updated info.
I have added this to my startup.cs:
services.AddSingleton();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
and
app.UseSession();
Setting the Session variable:
https://i.imgur.com/CY8rcdk.png
Using the Session variable:
https://i.imgur.com/SuLJKzV.png
Variable is always null.
Thank you for trying to help.
HttpContext is accessible only from things that are request specific, since it's a context of one and only request. And new controller instances are created by the framework for each request, with injected HttpContext. It's the developers job to pass it further if the need arises.
I recommend reading this article about it: https://dotnetcoretutorials.com/2017/01/05/accessing-httpcontext-asp-net-core/
First in your startup.cs, you need to register IHttpContextAccessor as a service like so :
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
When you create a helper/service class, you can then inject in the IHttpContextAccessor and use it. It would look like something not too dissimilar to this :
public class UserService : IUserService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public bool IsUserLoggedIn()
{
var context = _httpContextAccessor.HttpContext;
return context.User.Identities.Any(x => x.IsAuthenticated);
}
}

.Net Core Use Session in Class

I am using Session in .Net core, However i am able to set and get the Session data in Controller like
HttpContext.Session.SetString("User", "True");
var user = HttpContext.Session.GetString("User");
But when i am trying to use the same code in a concrete class i am not able to do so. It does not show GetString or SetString after HttpContext.Session.
It does not work after
HttpContext.Session
Please help
Thanks
To access session in non-controller class -
First, register the following service in Startup.ConfigureServices
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Now, register a class (example - SessionManager) where you want to access the Session in Startup.ConfigureServices.
services.AddScoped<SessionManager>();
Now, in SessionManager class, add the following code.
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISession _session;
public SessionManager(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_session = _httpContextAccessor.HttpContext.Session;
}
The above code is receiving IHttpContextAccessor object through dependency injection and then, it is storing Sessions in a local variable.
That's because HttpContext is a member of Controller, and outside a controller, it's a type name. See Access the current HttpContext in ASP.NET Core how to inject the IHttpContextAccessor into a class and access the session from there.
However, it's generally inadvisable to use the session in a class library. You'd better pass the particular values to your library call. So instead of accessing the settings in the library method, you do:
// controller code
var user = HttpContext.Session.GetString("User");
var libraryResult = _fooLibrary.Bar(user);
HttpContext.Session.SetString("UserResult", libraryResult);

How to get current user's data in Controller's constructor?

I need to use some current user's data in controller's constructor to initialize repositories, but looks like impossible to get current user's data, because all objects, which can give any data about the user (for example HttpContext or User) is nullable in constructor's area. How can I get needed data inside the constructor or any other way initialize repositories with user's data?
You can use IHttpContextAccessor to access HttpContext in constructor;
public class HomeController : Controller
{
public HomeController(IHttpContextAccessor httpContextAccessor)
{
var httpContext = httpContextAccessor.HttpContext;
}
}
If you are using ASP.NET Core 1.x register IHttpContextAccessor in configure method in startup class and for ASP.NET Core 2.0 I think it is not required.
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
Why in the constructor?
There are four ways you can get the current user's data in MVC:
You can create a session for the current user's data (preferred)
You can store data in local storage.
Using cookies. But that depends on cookies being allowed in the user's browser.
MVC provides ViewBag. You can pass data by using it.

Categories

Resources