Blazor server scoped service is different - asp.net-core

I need to store the authenticated user Session Id, where access token is retrieved from dictionary per user session.
I have simple Scoped service services.AddScoped<SessionService>();
public class SessionService
{
public string SessionId { get; set; }
}
In MainLayout.razor I'm injecting it and setting it when the user log in. Which works fine.
#inject SessionService SessionService
.....
#code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationStateTask { get; set; }
protected override async Task OnInitializedAsync()
{
SessionService.SessionId = (await AuthenticationStateTask).User.GetSessionId();
}
}
However, I'm creating HttpClient from IHttpClientFactory and getting the access token based on the user Session Id, but all 3 different approaches to get the SessionService have SessionId set to null
services.AddHttpClient("backend", async (provider, client) =>
{
var httpContextAccessor = provider.GetService<IHttpContextAccessor>();
var httpContextService = httpContextAccessor.HttpContext.RequestServices.GetService<SessionService>();
using var scope = provider.CreateScope();
var anotherProvider = services.BuildServiceProvider();
var anotherSession = anotherProvider.GetService<SessionService>();
var sessionId = scope.ServiceProvider.GetService<SessionService>()?.SessionId;
client.BaseAddress = ...;
});
If I use the service inside component the SessionId is what it has to be and not null. Why is that happening and how to fix it?

Related

Read all Request Headers and assign it to a global object

I am building a webapi in .Net Core 6.0.
public class UserDetails
{
public int UserId { get; set; }
public string UserName { get; set; }
}
I want to read the user details from the httpcontext.Request and initialize this UserDetails object and store the values in the properties.
And use the same object of UserDetails across the application to read the userId and userName.
All the online articles suggest using ConfigureServices() and add a singleton of the UserDetails class.
But the problem is I wont have access to httpContext in the startup.cs/program.cs.
is there a better way of doing it?
EDIT1:
My Middleware :
public class UserDetailsMiddleWare
{
private readonly RequestDelegate _next;
public UserDetailsMiddleWare(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
UserDetails userDtls = new UserDetails()
{
UserId = httpContext.Request.Headers["userid"],
UserName = httpContext.Request.Headers["username"]
};
await _next(httpContext);
}
}
My Program.cs file:
services.AddScoped<UserDetails>();
app.UseMiddleware<UserDetailsMiddleWare>();
app.Use((context, next) =>
{
// Get the scoped service from middleware
var userDetails = context.RequestServices.GetRequiredService<UserDetailsMiddleWare>();
// Populate the user details from the request so that anyone else that resolves this service will have the correct details.
userDetails.InvokeAsync(context);
return next(context);
});
My Service:
public class MyService: IMyService
{
private readonly IMyGateway _myGateway;
private UserDetails _details;
public MyService(IMyGateway myGateway, UserDetails details)
{
_myGateway = myGateway;
_details = details;
}
public async Task<string> myMethod()
{
string userid =_details.UserId;
return await _myGateway.GenerateAsync();
}
}
It's worthwhile thinking about the lifetime of objects when reasoning through solving this problem. The reason you don't have an http request at startup is because the server isn't even listening for requests yet. The lifetime of the UserDetails object should match the lifetime of the incoming request (because that's where it's getting the details from). If you made UserDetails a singleton, which is a single instance for ALL requests, which user's details would it have? Which request? A singleton is NOT what you want in this case.
So breaking the problem down some more, you need to populate the user details object in a place where you have access to the request. The most obvious place for doing that would be in ASP.NET Core middleware.
Now to have that UserDetails object flow to other services, you'd need to make it a scoped service.
services.AddScoped<UserDetails>();
...
app.Use((context, next) =>
{
// Get the scoped service from middleware
var userDetails = context.RequestServices.GetRequiredService<UserDetails>();
// Populate the user details from the request so that anyone else that resolves this service will have the correct details.
userDetails.UserId = httpContext.Request.Headers["userid"];
userDetails.UserName = httpContext.Request.Headers["username"];
return next(context);
});

Is there a way to use any IDistributedCache as a ResponseCache in .net core?

I want to cache responses from APIs to DistributedSqlServerCache.
The default ResponseCaching only uses a memory cache. There is a constructor which allows to configure what cache to use, but it's internal.
I wrote a filter. If the response is not cached and the http response is OK and the ActionResult is an ObjectActionResult, it serializes the value as JSON and saves it to SQL cache.
If the response is cached, it deserializes it and sets the result as an OkObject result with the deserielized object.
It works ok, but it has some clumsy things (like, to use the attribute, you have to specify the type which will be de/serialized, with typeof()).
Is there a way to cache responses to a distributed sql cache, which doesn't involve me hacking together my own mostly working solution?
Another option would be to copy-pasta the netcore ResponseCacheMiddleWare, and modify it to use a diffirent cache. I could even make it a nuget package maybe.
Are there any other solutions out there?
Here's the filter I put together (simplified for display purposes)
namespace Api.Filters
{
/// <summary>
/// Caches the result of the action as data.
/// The action result must implement <see cref="ObjectResult"/>, and is only cached if the HTTP status code is OK.
/// </summary>
public class ResponseCache : IAsyncResourceFilter
{
public Type ActionType { get; set; }
public ExpirationType ExpirationType;
private readonly IDistributedCache cache;
public ResponseCache(IDistributedCache cache)
{
this.cache = cache;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext executingContext, ResourceExecutionDelegate next)
{
var key = getKey(executingContext);
var cachedValue = await cache.GetAsync(key);
if (cachedValue != null && executingContext.HttpContext.Request.Query["r"] == "cache")
{
await cache.RemoveAsync(key);
cachedValue = null;
}
if (cachedValue != null)
{
executingContext.Result = new OkObjectResult(await fromBytes(cachedValue));
return;
}
var executedContext = await next();
// Only cache a successful response.
if (executedContext.HttpContext.Response.StatusCode == StatusCodes.Status200OK && executedContext.Result is ObjectResult result)
{
await cache.SetAsync(key, await toBytes(result.Value), getExpiration());
}
}
private async Task<byte[]> toBytes(object value)
{
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, value, ActionType);
return stream.ToArray();
}
private async Task<object> fromBytes(byte[] bytes)
{
using var stream = new MemoryStream(bytes);
using var reader = new BinaryReader(stream, Encoding.Default, true);
return await JsonSerializer.DeserializeAsync(stream, ActionType);
}
}
public class ResponseCacheAttribute : Attribute, IFilterFactory
{
public bool IsReusable => true;
public ExpirationType ExpirationType;
public Type ActionType { get; set; }
public ResponseCacheAttribute(params string[] queryParameters)
{
this.queryParameters = queryParameters;
}
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var cache = serviceProvider.GetService(typeof(IDistributedCache)) as IDistributedCache;
return new ResponseCache(cache)
{
ExpirationType = ExpirationType,
ActionType = ActionType
};
}
}
}
In the end I made a nuget package, sourced on github. See this issue for some more context as to why a new package was made.

ASP.NET CORE Add easily accessible properties to logged user

In our Asp.Net Core (2.2) MVC project we had to use an existing database (including all user & role related tables) from our previous Asp.Net Web app project.
Retrieving user data in asp.net web app (and having it available throughout the website) was preatty simple: upon login fill a custom user class/object with all the properties you need, save it as a Session variable and you call it wherever you need it (without going to the database).
This seems to me a lot harder to achieve in Asp.Net Core. What I have so far is:
ApplicationUser class:
public class ApplicationUser : IIdentity
{
public int Id { get; set; }
public Uporabnik Uporabnik { get; set; }
public string AuthenticationType { get; set; }
public bool IsAuthenticated { get; set; }
public string Name { get; set; }
}
Login form:
public IActionResult Prijava(PrijavaModel model)
{
// check user credentials
//
// ... validation code here ...
//
if (uporabnik != null)
{
//Create the identity for the user
var identity = new ClaimsIdentity(new[] {
new Claim("Email", model.Email),
new Claim("Id", uporabnik.IdWebUser.ToString()),
new Claim("Name", uporabnik.ImeInPriimek),
new Claim(ClaimTypes.Name, uporabnik.ImeInPriimek),
new Claim(ClaimTypes.PrimarySid, uporabnik.IdWebUser.ToString())
}, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var login = HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return RedirectToAction("Index", "Home");
}
return View();
}
Now to retrieve the data in a controller I have to do something like this:
// id
int idUser = int.Parse(#User.Claims.FirstOrDefault(x => x.Type == "Id").Value);
// or email
string email = #User.Claims.FirstOrDefault(x => x.Type == "Email").Value;
Well, this all works fine, but it's definitely not practical. To access any other user data I can go to the database (by "ID") and retrieve it, but I don't think this is the right way to do it!?!
Can I expand the identity class in such a way that I can set the extra properties I need at login time and retrive in a fashion similar to something like this:
var property1 = #User.Property1;
var property2 = #User.Property2;
// or
var property1 = #User.MyExtraProperties.Property1;
var property2 = #User.MyExtraProperties.Property2;
Is it possible (and also keeping it simple)?
EDIT: since there are no answers/suggestions, can I do the same thing with a different approach?
Look like you only want to call your properties in a better way?
public class ApplicationUser : IdentityUser
{
public string CustomName { get; set; }
}
Assuming you have done adding your extra properties, you could create an extension method for your properties, so you can later call them like User.Identity.GetCustomName().
namespace Project.Extensions
{
public static class IdentityExtensions
{
public static string GetCustomName(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("CustomName");
return (claim != null) ? claim.Value : string.Empty;
}
}
}
Note that I didn't include the part where you add the claims, because you already have it. In this case, you should have CustomName claim.
Also, #Dementic is right about the session. If a user is removed/disabled, he would still have access to. So, having a db call each time you need to fetch information is correct.

Proper way to get current User ID in Entity Framework Core

There are a bunch of different answers floating around here for the different RC's of ASP.NET Core on how to get the ID of the currently logged in user. I wanted to ask the definite question here. Please note that project.json now has "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0"
With RC1, you could do something like this:
using Microsoft.AspNet.Identity;
using System.Security.Claims;
User.GetUserId();
But with the newly released version 1 of EF Core, Microsoft.AspNet.Identity is not the right version.
There was suggestions to use UserManager, which seems like a lot just to get the currently logged in user:
private Task<ApplicationUser> GetCurrentUserAsync() => _userManager.GetUserAsync(HttpContext.User);
var user = await GetCurrentUserAsync();
var userId = user?.Id;
Another method that I found was:
private readonly UserManager<ApplicationUser> _userManager;
_userManager.GetUserId(User)
So with ASP.NET Core 1 RTM and EF Core 1 with the following libraries in project.json, what is the proper way to get the id of the currently logged in user?
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
If you are accessing this from withing the Controller, then using UserManager to get the user ID is pretty inefficient as you are making a round trip to the database. If you are using ClaimsIdentity, you can do something like this to get the user id:
var claimsIdentity = (ClaimsIdentity)this.User.Identity;
var claim = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
var userId = claim.Value;
This method just reads the user ID which is already present in the cookie, which in turn is automatically deserialized and stored in a ClaimsIdentity instance.
I use this helper class:
public static class UserHelpers
{
public static string GetUserId(this IPrincipal principal)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
var claim = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
return claim.Value;
}
}
So getting a user ID becomes:
var userId = this.User.GetUserId();
If, for some reason, the required claim is not present in the Claims colleciton, you can easily add it when creating the user's ClaimsIdentity:
public class ApplicaionUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
userIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, this.UserId));
return userIdentity;
}
}
ASP.NET Core Identity is injected via DI in the startup.cs - as such you just have to inject UserManager via a constructor
UserManager<ApplicationUser> userManager
You can then use the following in methods
_userManager.GetUserId(User);
That's the way its used in the Sample Web Application when you create a new ASP.NET Core 1 project with Individual User Account.
The one-liner below is a more concise version of the other answers above.
var user = User.FindFirst(ClaimTypes.NameIdentifier).Value;
To explain a little further, I wanted to use the most basic form of authentication without any tables in the database so I chose this one -
Using Cookie Authentication without ASP.NET Core Identity from the Core documentation.
To get this working, the first step is to add the services in Startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = new PathString("/Account/Login/");
options.LogoutPath = new PathString("/Account/Logoff/");
options.AccessDeniedPath = new PathString("/Account/AccessDenied/");
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
services.ConfigureApplicationCookie(identityOptionsCookies =>
{
// See https://andrewlock.net/automatically-validating-anti-forgery-tokens-in-asp-net-core-with-the-autovalidateantiforgerytokenattribute/
identityOptionsCookies.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
Then in the AccountController on the post back having entered a valid user id and password, the simplest Claims based authentication is to just add the login id as a Claim, e.g.
var claims = new List
{
new Claim(ClaimTypes.NameIdentifier, loginViewModel.Guid, ClaimValueTypes.String, issuer),
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(_cookieTimeoutInMinutes),
IsPersistent = true,
AllowRefresh = false
});
Once the Sign In completes you can retrieve the user id as described in the one liner above. See the answer from Milos Mrdovic above for the more detailed steps.
var user = User.FindFirst(ClaimTypes.NameIdentifier).Value;
See Claims-Based Authorization for further information.
You can get UserId by this way also.
public class Program
{
private readonly SignInManager<ApplicationUser> _signInManager;
public Program(SignInManager<ApplicationUser> signInManager)
{
_signInManager = signInManager;
var UserId = _signInManager.Context.User.Claims.FirstOrDefault().Value;
}
}
Where ApplicationUser class is given below....
public class ApplicationUser:IdentityUser
{
[Column(TypeName = "Nvarchar(500)")]
public string FirstName { get; set; }
[Column(TypeName = "Nvarchar(500)")]
public string MiddleName { get; set; }
[Column(TypeName = "Nvarchar(500)")]
public string LastName { get; set; }
[Column(TypeName = "DateTime")]
public DateTime? LastAccess { get; set; }
}
And Your ApplicationUser class should inherited by IdentityUser.

Uni Test for Log in Page giving null

i am writing a unitest for login in my project .
when i call the login function of my controller. Memership.GetUser giving null value for passed User.
below is Test case
[TestMethod]
public void Login()
{
//Arrange
AccountController account = new AccountController(_forgotPasswordTokensRepo, _IMessageTemplateDAO, _IEmailService, _ISettingDAO, _IProfileDAO);
List<AccountBO> TestUsers = new List<AccountBO>();
AccountBO objAccountBO = new AccountBO();
objAccountBO.Email = "uu#yopmail.com";
objAccountBO.Password = "123456789";
TestUsers.Add(objAccountBO);
var result = (JsonResult)account.Login(TestUsers[0]);
var json = new System.Web.Script.Serialization.JavaScriptSerializer();
string data = result.Data.ToString().Split('=')[1].Trim();
bool Processdata = Convert.ToBoolean(data.Replace('}', ' ').Trim());
Assert.AreEqual<bool>(true, Processdata);
}
Controller function is
public JsonResult Login(AccountBO account, string returnUrl = "")
{
bool hasBeenUnlocked = false;
if (ModelState.IsValid)
{
MembershipUser adminUser;
adminUser = Membership.GetUser(account.Email);
------so on
}
}
here adminUser = Membership.GetUser(account.Email) is giving null.
What do you expect GetUser to return? Are you expecting it to query the db/active directory and return a user with full credentials?
Unless I remember wrong, Membership is part of Asp.Net infrastructure so it is not set up in the context of your unit test. The solution is not to set it up. It is to stub out that functionality.
public interface IProvideUsers {
public MembershipUser Get(string email, string password);
}
public class AspNetMembershipProvider : IProvideUsers {
public MembershipUser Get(string email) {
//...
}
}
then in your controller
IProvideUsers users;
....
users.Get(account.Email, account.Password);
where users is injected via the constructor. Then in your tests you create your own implmentation of IProvideUsers and provide that.