Asp.Net 5 / Identity 3: Caching of Claims in the IdentityDbContext implementation - c#

While looking for a way to be able to assign and revoke roles via an admin controller for users other than the one making a request, I've implemented a custom IAuthorizeFilter that checks if Guid tag, stored as a Claim, matches to a value in the Entity Framework 7 Code First Identity table for UserClaims.
Essentials, it's this code:
public class RefreshUserClaimsFilterAttribute : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
var User = context.HttpContext.User;
var dbContext = context.HttpContext.ApplicationServices.GetRequiredService<ApplicationDbContext>();
var stampFromClaims = User.Claims.FirstOrDefault(Claim => Claim.Type == "ClaimsStamp")?.Value;
var stampFromDb = dbContext.UserClaims.Where(UserClaim => UserClaim.UserId == User.GetUserId()).ToList().FirstOrDefault(UserClaim => UserClaim.ClaimType == "ClaimsStamp")?.ClaimValue;
// Update claims via RefreshSignIn if necessary
}
}
I'm having the problem at the line where I'm assigning var stampFromDb, it could be much more readable in the following way:
var stampFromDb = dbContext.UserClaims.FirstOrDefault(UserClaim => UserClaim.UserId == User.GetUserId() && UserClaim.ClaimType == "ClaimsStamp")?.ClaimValue;
That, however, gives me cached (the same values as the actual claims from User.Identity) results and I could not find any documentation on this. My best guess is that the error is somewhere on my side, but I've never encountered such a problem before. This is the first time I'm using Asp.Net 5 and EF7. I'm using the default connection (LocalDB) to SQL Server 12.0.2000.
Is this a feature and, if yes, can it be turned off or did I make a mistake somewhere?

The issue was caused due to there being two different ways to create a service via dependency injection:
The sample code in my question used
var dbContext = context.HttpContext.ApplicationServices.GetRequiredService<ApplicationDbContext>();
where it should use
var dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
The difference here is between ApplicationServices and RequestServices. It looks like the ApplicationServices injector does have an instance of the database context somewhere which has had the DbSet filled earlier and therefore returning cached data instead of doing a database query.

Related

ASP.NET Core Web API: how can I register a value derived from the request context?

In ASP.NET Core Web API, I'd like to define an endpoint like this (example):
app.MapGet("userId", (User user) => user.id);
In this example, the endpoint exposed to the caller should take no parameters. The User object should be derived from information present in the request (e.g. in the header), but the method definition shouldn't need to know that detail - it should just be able to access User as a dependency.
I thought that I could accomplish this by specifying a resolution for the User dependency similar to this:
builder.Services.AddScoped(typeof(User), p =>
{
var cont = p.GetService<HttpContext>();
return new User(cont.Request.Headers["userKey"].ToString());
});
However, when I run this, the p.GetService<HttpContext>() returns null.
How can I create a request-scoped dependency resolution that incorporates data from the request? Is this a good approach, but I'm just not looking for the right service? Or is this approach not going to work?
Try using the HttpContextAccessor
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped(typeof(User), p =>
{
var httpContextAccessor = p.GetService<IHttpContextAccessor>();
var httpContext = httpContextAccessor.HttpContext;
(...)
});

Global Filters on a Multitenancy Application with EF Core 6 + GraphQL on a single database approach

how have you been?
Can someone help us with these multi-tenancy questions and best practices with EF Core 6.0?
We are working on a multi-tenancy application exposing data through a GraphQL endpoint. We are running with .NET 5.0, EF Core 6, GraphQL and SQL Server.
Our setup is running fine. There are more than 45 tenants on this solution, and each Tenant has their Web Application that connects on a single GraphQL endpoint. The only thing that changes for each Tenant is the data, that is logically separated by a "TENANT_ID" column.
The approach and pattern that we decided for use in this project is based on a shared database with no schema customization (only dbo). In resume: One database, one schema, one GraphQL endpoint and multiple websites consuming these services. Websites that connect on the GraphQL need to pass a JWT and a Tenant ID. This field "TenantID" passed in the header is used for allowing the filter on the server side.
Example: Advertises.Where(a => a.TenantID == x);
We are studying the best practices to filter the data based on a TENANT_ID passed from the client to the server using some HTTP headers.
EF Core 6 has the Global Query Filters, but it seems is not possible to apply the filter, because the TenantID changes for every requisition.
The EF Core OnModelCreating method is always called once per AppDomain per DbContext, but we need to change this value for every request.
Does anyone recommend an approach to apply EF Query Filters using a external ID came from a http request?
In our research, we found some tips to inject the IHttpContextAccessor on the database layer (thus, to retrieve the headers, and apply the filters based on the TenantID), but, I confess that I'm not confortable for using the AspNetCore.Http namespace on the database layer.
Thank you all.
Claims are the way.
After some research, we found out that through the query type operations, it's possible to receive a ClaimsPrincipal object, a class from the System.Security.Claims namespace.
Therefore, it is possible to send some HTTP headers that contain your Tenant ID. This value could be your KEY for filtering. In my case, that was a Guid type.
In this sample, we are passing an object called "claims" which is a ClaimsPrincipal.
[Authorize(Policy = "YourPolicyName"), UseDbContext(typeof(YourDatabaseContext)), UseOffsetPaging, UseFiltering, UseSorting]
public IQueryable<User> Users(ClaimsPrincipal claims, [Service] YourDatabaseContext context)
{
var tenantID = claims.Get(ClaimTypes.GroupSid);
return context.Users.Where(u => u.TenantID == tenantID);
}
After that, we just needed to create a extension method to retrieve data from the Claim:
public static class ClaimsExtensions
{
public static Guid Get(this ClaimsPrincipal claimsPrincipal, string claimType)
{
var element = claimsPrincipal.FindFirstValue(claimType);
if (element is not null)
{
if (Guid.TryParse(element, out Guid id))
return id;
}
return Guid.Empty;
}
}
That's it. Don't forget it is needed to pass the SID on your HTTP Request.

Get data from a table after having closed the session

In this moment I´m try to get a List of users and checks if the user is in the BD or not
I´m using Web API Net 6 and Sql Server
This is the code
[HttpPost("login")]
public async Task<ActionResult<string>> Login(LoginDto request)
{
//In this line I´m try to get the list of users (this bottom line doesn't work)
await _context.Users.ToListAsync();
if(user.UserName != request.UserName)
{
return BadRequest("User Not Found");
}
// ...
Here the problem is that the program has been running for 1 time until it works normally but when I end the session and come back again there is an application on the 2nd time it can no longer find the user in the database. My idea then is to add that line of code that just doesn't work (I don't know if it's due to await or if it's wrong to get through ListAsync() or if it's due to the user inside the if not being connected with the _context of the database )
By the way, that user is static having declared it like this
-> public static User user = new User();
Can anyone help me with this problem or tell me better solutions on how to get data from a table
If you just want to search your Users table for a user record with the name passed in the LoginDTO instance, then you just ask it to the database context to search for that name.
var userInDb = await _context.Users.FirstOrDefaultAsync(x => x.UserName == request.UserName);
if(userInDb == null)
... not found ....
But let me understand better your problem. If you are implementing your custom authorization and verification infrastructure for users, then think twice becase is not as simple as it looks. (For example, how do you store passwords in that table?) There is a dedicated library for that from Microsoft and is called ASP.NET Identity

AspNet MVC get User or Identity object by username for any user (not current user)

I have been looking for a way to get a complete Userobject based on username in the default Identity model of AspNet MVC for any user. I am using asp.net Identity 2.
Through some google searches the following is the closest I came without directly querying to the database.
var user = UserManager.Users.FirstOrDefault(u => u.UserName == userName);
However UserManager requires a generic type of UserManager<TUser> and I have no clue what gereric type I am supposed to fill out here, or how I am even supposed to make this work. I'd prefer using the default asp.net functions so I don't have to query to the database myself.
I'm going to assume by default you mean the set up Visual Studio gives you when you choose
an ASP.NET Web Application with MVC and individual user accounts.
You will need the following using statement
using Microsoft.AspNet.Identity;
I believe you want the FindByName method or the FindByNameAsync method. It is a generic method so you can call it like this
var user = userManager.FindByName<ApplicationUser, string>("{the username}");
where in this case the type of the user primary key is string. But you should be able to drop the generic type parameters and just call it like this
var user = userManager.FindByName("{the username}");
If you aren't using this default set up the thing to do would be to find the type that your userManager inherits from. In visual studio you can do this by hovering over the ApplicationUserManager and pressing F12. In the default case it looks like this
public class ApplicationUserManager: UserManager<ApplicationUser>
{
//...
}
But whatever the type that the UserManager has is what you need.
ApplicationDbContext context = new ApplicationDbContext();
string CurrentUser = User.Identity.Name;
var applicationUser = await context.Users
.Where(a => a.Email.Equals(CurrentUser)).FirstOrDefaultAsync();

Prevent duplicate entries in database during POST call

I have an API written in Asp Net Core 2.1 (upgraded from 2.0) which build an entity from a DTO sent by a mobile app.
The DTO have a field "mobileId" (Guid format) to prevent the mobile to send the same object when it goes online (after connectivity issues for example).
But this solution does not seem to be efficient as presented below :
There are 4 lines whereas I actually wanted only 1 line :S I don't understand how it occurred because I specified in the Startup:
services.AddScoped<DbContext>(s =>
{
// code emitted for brevity
});
The code of the API itself is centralized in a Handler because our API follow a little piece of CQRS pattern and the "Dispatcher" is registered via Autofac :
public class DispatcherModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Dispatcher>()
.As<IDispatcher>()
.InstancePerDependency();
}
}
The same applies for IUnitOfWork which use our scoped DbContext internally
At the beginning I check that an entity with the same 'mobileId' is not already present in database :
if (dto.MobileId != null)
{
Report existingMobileId = await UnitOfWork.Repository<Report>()
.GetFirstOrDefaultAsync(qr => qr.MobileId == dto.MobileId);
if (existingMobileId != null)
{
return new Result<object>(new Error(400, $"Report with '{dto.MobileId}' already exists in DB."));
}
}
What do you think I'm doing wrong ? Or maybe I should add something else ?
Thank you for your help guyz :)
Technical environment :
- ASP.NET Core 2.1
- Entity Framework Core
- Azure SQL Database
AddScoped means that new instance of service is created for each request, so, if you have several simultaneous requests, you may have some kind of race condition on repository level when each checks presence of a row before writing it to db. I would recommend putting this responsibility on the database level applying Unique constraint on mobileId column.

Categories

Resources