AuthenticationState always returns null after successful log in - asp.net-core

So i have this set up in another project, but when i have come to use exactly the same method, it always returns null
#code {
[CascadingParameter]
private Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> authState { get; set; }
private System.Security.Claims.ClaimsPrincipal principal;
public string displayName { get; set; }
private bool collapseNavMenu = true;
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
protected async override void OnParametersSet()
{
principal = (await authState).User;
displayName = principal.Claims.FirstOrDefault(c => c.Type == "display_name").Value;
}
}
I can't figure out why this is the case, the startup class configure and configure services are pretty much identical. Can anyone point me in the direction on why this maybe the case?
Thanks

I ran into AuthenticationState being null in a Blazor Service App. Check that <CascadingAuthenticationState>
is wrapped around the contents of the App.razor file.
See https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0

Related

Blazor: How can i check if the Param for the Callback function does exsist?

Example:
/Parent
<SomeTemplate event="#ThisEvent"/>
#code{
private async Task ThisEvent()
{
}
/Child
#Code {
[Parameter]
public EventCallback event{ get; set; }
}
Is there a way to "check" if "event" has been passed to the Child as parameter ?
I want to use this in the Child to decide "what" this implementation of this Template should show and what not
The only Solution i have is to pass additional bool values to do so, im looking for a away to avoid that
Here's how to check if a delegate has been assigned to the callback. I've set up the check to throw a Debug break if nothing has been assigned.
Also note the EditorRequired setting on the parameter which will produce a warning in the editor if that's what you want.
#using System.Diagnostics;
<h3>CallbackComponent</h3>
#code {
[Parameter, EditorRequired] public EventCallback<int> MyEventCallback { get; set; }
protected override void OnInitialized()
{
Debug.Assert(this.MyEventCallback.HasDelegate);
}
private async Task OnClick(MouseEventArgs e)
{
await this.MyEventCallback.InvokeAsync(1);
}
}
Checking for whether a delegate has been assigned is what you are after:
public EventCallback MyEventCallback { get; set; }
public async Task MyHandler()
{
if (MyEventCallback.HasDelegate)
{
await MyEventCallback.InvokeAsync();
}
}

Blazor : How to read appsetting.json from a class in .NET 6?

The following is working for me, but not sure this is the right way to do use DI in .NET6 blazor.
I have the following class
public class Authentication
{
private IConfiguration _configuration;
private AppState _appState;
public Authentication(IConfiguration Configuration, AppState appState)
{
_configuration = Configuration;
_appState = appState; ;
}
public async Task<AccessToken?> getAccessToken()
{
var tokenServer = _configuration.GetValue<string>("tokenUrl");
var clientID = _configuration.GetValue<string>("ABC:ClientID");
var clientSecret = _configuration.GetValue<string>("ABC:ClientSecret");
var grantType = _configuration.GetValue<string>("ABC:GrantType");
AccessToken? accessToken = null;
.............
............
return accessToken;
}
}
in my code behind of razor page
namespace XXXXXXXXXXX.Pages
{
public partial class Index
{
[Inject]
public ILogger<Index> _Logger { get; set; }
[Inject]
public IConfiguration Configuration { get; set; }
[Inject]
public AppState _appState { get; set; }
**Authentication auth;**
protected override void OnInitialized()
{
**auth = new Authentication(Configuration, _appState);**
base.OnInitialized();
}
private async Task HandleValidSubmit()
{
_Logger.LogInformation("HandleValidSubmit called");
auth.getAccessToken();
// Process the valid form
}
}
}
My Question is I was Expecting the DI to do its magic and Insert the Dependency in my class.
but to get this working i had to write
auth = new Authentication(Configuration, _appState);
I was expecting to instantiate
using auth = new Authentication() , but this one throws compiler error.

How do I get the current logged in user ID in the ApplicationDbContext using Identity?

I have created a .net core 2.1 MVC application using the template in Visual Studio with the Identity preset (user accounts stored in the application) and I am trying to automate some auditing fields.
Basically what I'm trying to do is overriding the SaveChangesAsync() method so that whenever changes are made to an entity the current logged in user ID is set to the auditing property of CreatedBy or ModifiedBy properties that are created as shadow properties on the entity.
I have looked at what seems to be tons of answers and surprisingly none of them work for me. I have tried injecting IHttpContext, HttpContext, UserManager, and I either can't seem to access a method that returns the user ID or I get a circular dependency error which I don't quite understand why it is happening.
I'm really running desperate with this one. I think something like this should be really straightforward to do, but I'm having a real hard time figuring out how to do it. There seem to be well documented solutions for web api controllers or for MVC controllers but not for use inside the ApplicationDbContext.
If someone can help me or at least point me into the right direction I'd be really grateful, thanks.
Let's call it DbContextWithUserAuditing
public class DBContextWithUserAuditing : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
public string UserId { get; set; }
public int? TenantId { get; set; }
public DBContextWithUserAuditing(DbContextOptions<DBContextWithUserAuditing> options) : base(options) { }
// here we declare our db sets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.NamesToSnakeCase(); // PostgreSQL
modelBuilder.EnableSoftDelete();
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
ChangeTracker.ProcessModification(UserId);
ChangeTracker.ProcessDeletion(UserId);
ChangeTracker.ProcessCreation(UserId, TenantId);
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
ChangeTracker.DetectChanges();
ChangeTracker.ProcessModification(UserId);
ChangeTracker.ProcessDeletion(UserId);
ChangeTracker.ProcessCreation(UserId, TenantId);
return (await base.SaveChangesAsync(true, cancellationToken));
}
}
Then you have request pipeline and what you need - is a filter hook where you set your UserID
public class AppInitializerFilter : IAsyncActionFilter
{
private DBContextWithUserAuditing _dbContext;
public AppInitializerFilter(
DBContextWithUserAuditing dbContext
)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
string userId = null;
int? tenantId = null;
var claimsIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
var userIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
userId = userIdClaim.Value;
}
var tenantIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == CustomClaims.TenantId);
if (tenantIdClaim != null)
{
tenantId = !string.IsNullOrEmpty(tenantIdClaim.Value) ? int.Parse(tenantIdClaim.Value) : (int?)null;
}
_dbContext.UserId = userId;
_dbContext.TenantId = tenantId;
var resultContext = await next();
}
}
You activate this filter in the following way (Startup.cs file)
services
.AddMvc(options =>
{
options.Filters.Add(typeof(OnRequestInit));
})
Your app is then able to automatically set UserID & TenantID to newly created records
public static class ChangeTrackerExtensions
{
public static void ProcessCreation(this ChangeTracker changeTracker, string userId, int? tenantId)
{
foreach (var item in changeTracker.Entries<IHasCreationTime>().Where(e => e.State == EntityState.Added))
{
item.Entity.CreationTime = DateTime.Now;
}
foreach (var item in changeTracker.Entries<IHasCreatorUserId>().Where(e => e.State == EntityState.Added))
{
item.Entity.CreatorUserId = userId;
}
foreach (var item in changeTracker.Entries<IMustHaveTenant>().Where(e => e.State == EntityState.Added))
{
if (tenantId.HasValue)
{
item.Entity.TenantId = tenantId.Value;
}
}
}
I wouldn't recommend injecting HttpContext, UserManager or anything into your DbContext class because this way you violate Single Responsibility Principle.
Thanks to all the answers. In the end I decided to create a UserResolveService that receives through DI the HttpContextAccessor and can then get the current user's name. With the name I can then query the database to get whatever information I may need. I then inject this service on the ApplicationDbContext.
IUserResolveService.cs
public interface IUserResolveService
{
Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext);
}
UserResolveService.cs
public class UserResolveService : IUserResolveService
{
private readonly IHttpContextAccessor httpContextAccessor;
public UserResolveService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public async Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext)
{
var currentSessionUserEmail = httpContextAccessor.HttpContext.User.Identity.Name;
var user = await dbContext.Users
.SingleAsync(u => u.Email.Equals(currentSessionUserEmail));
return user.Id;
}
}
You have to register the service on startup and inject it on the ApplicationDbContext and you can use it like this:
ApplicationDbContext.cs
var dbContext = this;
var currentSessionUserId = await userResolveService.GetCurrentSessionUserId(dbContext);

DataContext.Users returns empty result in Razor pages

Hi I have a problem accessing Users table from a Razor page in ASP.NET Core
I Created an AppDataContext class that extends IdentityDbContext<Models.User, Models.UserRole, string>
I can use it with other controller classes and services without problems. But when I start working on razor pages the dataContext.Users always return empty enumerable. Other DbSets still working properly, only the Users not work.
This also happens when I try to access data from other services like UserManager.Users or SigniInManager.UserManager.Users
Here's some part of my files
AppDataContext
public class AppDataContext : IdentityDbContext<Models.User, Models.UserRole, string>
{
// Other DbSet's
public AppDataContext(DbContextOptions<AppDataContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Other entities building
MapIdentityTables(builder);
}
private void MapIdentityTables(ModelBuilder builder)
{
builder.Entity<Models.User>().ToTable("Users");
builder.Entity<IdentityUser>().ToTable("Users");
builder.Entity<Models.UserRole>().ToTable("UserRoles");
builder.Entity<IdentityRole>().ToTable("UserRoles");
builder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
builder.Entity<IdentityUserRole<string>>().ToTable("UserUserRoles");
builder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
builder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaims");
builder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
}
LoginPage.cshtml.cs
public class LoginModel : PageModel
{
private readonly Identity.AppUserManager userManager;
private readonly SignInManager<Models.User> signInManager;
private readonly Data.AppDataContext dataContext;
public LoginModel(Identity.AppUserManager userManager, SignInManager<Models.User> signInManager, Data.AppDataContext dataContext)
{
this.userManager = userManager;
this.signInManager = signInManager;
this.dataContext = dataContext;
}
public IList<Model.User> Users { get; private set; }
public void OnGet()
{
Users = userManager.Users.ToList(); // Empty
Users = signInManager.UserManager.Users.ToList(); // Empty
Users = dataContext.Users.ToList(); // Empty
}
}
User class
public class User : Microsoft.AspNetCore.Identity.IdentityUser
{
public ICollection<UserDevice> Devices { get; set; }
public IList<UserPassword> Passwords { get; set; }
}
Have I done anything wrong or am I missing something?
UPDATE
The problem is gone somehow now after I gave up and do something else. But it's not a solution since the original problem still there if I did the same.
What I have done was to revert all my changes and added AddSecondIdentity from this SO answer. Created StaffUser : IdentityUser and StaffUserManager<StaffUser, UserRole> (same UserRole as the original UserManager) to handle those new IdentityUser objects.
Then I just use StaffUserManager and SignInManager<StaffUser> instead of AppUserManager and SignInManager<User> in Login.cshtml.cs
public LoginModel(StaffUserManager userManager, SignInManager<Models.StaffUser> signInManager, Data.AppDataContext context)
{
var users = context.Users.ToList() // 1 user
}
Which now confuses me further. But I don't have time for this now. I think it has something to do with the Discriminator part of the database since the returned user is the one with StaffUser discriminator value but there are some others without the discriminator that are not returned.
Make sure you add services.AddIdentity
services
.AddIdentity<User, ApplicationRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
//lock out attempt
options.Lockout.AllowedForNewUsers = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 3;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
If this doesn't work please provide me your source code so I can take a look on it. Perhaps your github.

Store and access value obtained during startup from my controller

I am using the opened connect middleware to authenticate with a third party oidc provider and everything is up and running as I would expect. During the token exchange I exchange my auth code for an access token which is successful but I then need to store this bearer token for use later in subsequent requests. The token exchange is done as part of my startup class (by overriding the OpenIdConnectEvents during the OnAuthorizationCodeReceived method) in the asp.net core project and I need to store and access that token in my controllers.
As there is no “session” per se yet, what is the most effective (or recommended way) to store this token value from the startup class and make it accessible in my controllers?
Ive tried to use IMemoryCache but despite putting the value in the cache during this startup phase, when I try and access that cache in my controller, it is always empty.
Is there a better/preferred way of persisting values form the startup class for later use in the lifecycle?
I can see in HttpContext.Authentication.HttpAuthenticationFeature.Handler.Options I have access to all the OpenIdConnectOptions properties and settings for oidc, but nowhere can I see the actual token value that I stored after the token exchange.
I use a similar approach with Auth0 and JWT. I store some app_metadata on the claims server, retrieve, and use these values in my controllers for every request.
Startup.cs Configure
var options = new JwtBearerOptions
{
Audience = AppSettings.Auth0ClientID,
Authority = AppSettings.Auth0Domain
};
app.UseJwtBearerAuthentication(options);
app.UseClaimsTransformation(new ClaimsTransformationOptions
{
Transformer = new Auth0ClaimsTransformer()
});
AdminClaimType
public abstract class AdminClaimType : Enumeration
{
public static readonly AdminClaimType AccountId = new AccountIdType();
public static readonly AdminClaimType ClientId = new ClientIdType();
public static readonly AdminClaimType IsActive = new IsActiveType();
private AdminClaimType(int value, string displayName) : base(value, displayName)
{
}
public abstract string Auth0Key { get; }
public abstract string DefaultValue { get; }
private class AccountIdType : AdminClaimType
{
public AccountIdType() : base(1, "AccountId")
{
}
public override string Auth0Key => "accountId";
public override string DefaultValue => "0";
}
private class ClientIdType : AdminClaimType
{
public ClientIdType() : base(2, "ClientId")
{
}
public override string Auth0Key => "clientId";
public override string DefaultValue => "0";
}
private class IsActiveType : AdminClaimType
{
public IsActiveType() : base(3, "IsActive")
{
}
public override string Auth0Key => "isActive";
public override string DefaultValue => "false";
}
}
Auth0ClaimsTransformer
public class Auth0ClaimsTransformer : IClaimsTransformer
{
private string _accountId = AdminClaimType.AccountId.DefaultValue;
private string _clientId = AdminClaimType.ClientId.DefaultValue;
private string _isActive = AdminClaimType.IsActive.DefaultValue;
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
//TODO: Clean up and simplify AdminClaimTypes Transformer
foreach (var claim in context.Principal.Claims)
{
switch (claim.Type)
{
case "accountId":
_accountId = claim.Value ?? _accountId;
break;
case "clientId":
_clientId = claim.Value ?? _clientId;
break;
case "isActive":
_isActive = claim.Value ?? _isActive;
break;
}
}
((ClaimsIdentity)context.Principal.Identity)
.AddClaims(new Claim[]
{
new Claim(AdminClaimType.AccountId.DisplayName, _accountId),
new Claim(AdminClaimType.ClientId.DisplayName, _clientId),
new Claim(AdminClaimType.IsActive.DisplayName, _isActive)
});
return Task.FromResult(context.Principal);
}
BaseAdminController
//[Authorize]
[ServiceFilter(typeof(ApiExceptionFilter))]
[Route("api/admin/[controller]")]
public class BaseAdminController : Controller
{
private long _accountId;
private long _clientId;
private bool _isActive;
protected long AccountId
{
get
{
var claim = GetClaim(AdminClaimType.AccountId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _accountId);
return _accountId;
}
}
public long ClientId
{
get
{
var claim = GetClaim(AdminClaimType.ClientId);
if (claim == null)
return 0;
long.TryParse(claim.Value, out _clientId);
return _clientId;
}
}
public bool IsActive
{
get
{
var claim = GetClaim(AdminClaimType.IsActive);
if (claim == null)
return false;
bool.TryParse(claim.Value, out _isActive);
return _isActive;
}
}
public string Auth0UserId
{
get
{
var claim = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
return claim == null ? string.Empty : claim.Value;
}
}
private Claim GetClaim(AdminClaimType claim)
{
return User.Claims.FirstOrDefault(x => x.Type == claim.DisplayName);
}
}
Now in my controller classes that inherit from BaseAdminController I have access to:
AccountId
ClientId
IsActive
Auth0UserId
Anything else I want to add
Hope this helps.
So I figured it out. It is available on HttpContext via the AuthenticationManager:
var idToken = ((AuthenticateInfo)HttpContext.Authentication.GetAuthenticateInfoAsync("Cookies").Result).Properties.Items[".Token.id_token"];
Works a treat :)