Can't access Claim by it's key after login with ASP.NET Core Identity in .NET 6 Project - asp.net-core

I have a Blazor project in .NET 6. I am using ASP.NET Core Identity for login. After login, I have set some Claims with a custom Key. Here is the code:
Login Code:
var createdUser = await _userManager.FindByNameAsync(Input.UserName.Trim());
var LoginResult = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (LoginResult.Succeeded)
{
var userRoles = await _userManager.GetRolesAsync(createdUser);
var claims = new List<Claim>()
{
new Claim("UserId", createdUser?.Id),
new Claim("Username", createdUser?.UserName),
new Claim("OrganizationId", createdUser?.OrganizationId)
};
if(userRoles!= null && userRoles.Count > 0)
{
foreach (var role in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
}
var appIdentity = new ClaimsIdentity(claims);
var claimPrincpal = new ClaimsPrincipal(appIdentity);
Thread.CurrentPrincipal = claimPrincpal;
User.AddIdentity(appIdentity);
return LocalRedirect(returnUrl);
}
After Login I have a UserInfoService class and there I have some code to get the Claim value. Here is the code below:
public class UserInfoService : IUserInfoService
{
private readonly AuthenticationStateProvider _authProvider;
private readonly UserManager<ApplicationUser> _userManager;
public UserInfoService(
AuthenticationStateProvider authProvider,
UserManager<ApplicationUser> userManager)
{
_authProvider = authProvider;
_userManager = userManager;
}
public async Task<string> GetUserName()
{
var auth = await _authProvider.GetAuthenticationStateAsync();
var user = auth.User;
if (!user.Identity.IsAuthenticated)
{
return null;
}
else
{
var claimsIdentity = user.Identity as ClaimsIdentity;
var userName = claimsIdentity.FindFirst("Username")?.Value;
if (string.IsNullOrWhiteSpace(userName))
return null;
return userName;
}
}
}
And In my Program.cs file I have the below settings for identity:
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.SignIn.RequireConfirmedAccount = false)
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
But whenever I call GetUserName method to get the username from Claim, it returns null. I tried to debug the code and during debugging whenever the breakpoint comes upon this line (var claimsIdentity = user.Identity as ClaimsIdentity;), I hover the mouse and have the below information into the Claims Property, which I don't understand how to access. I don't even see the Claims Key (UserId, Username) which I set during the login.
Can anyone help to find the answer to how can I access Claims Key and Value?

var appIdentity = new ClaimsIdentity(claims);
var claimPrincpal = new ClaimsPrincipal(appIdentity);
Thread.CurrentPrincipal = claimPrincpal;
User.AddIdentity(appIdentity);
By using the above code, the new claim will be added to the HttpContext.User, it will store data while processing a single request. The collection's contents are discarded after a request is processed.
So, I suggest you can store the Claims in the AspNetUserClaims table, you could use the UserManager.AddClaimAsync() method to add the specified claim to the user, and then use the SignInManager or ApplicationDbContext to get tye user's claims. Check the following sample code (Login post method):
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
//find current user.
var user = await _userManager.FindByEmailAsync(Input.Email);
//based on user information to query the user and role policy table. Here I set the user role directly.
var userrole = "User";
if (user.UserName.Contains("aa"))
{
userrole = "Admin";
}
//get the current user claims principal
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(user);
//get the current user's claims.
var claimresult = claimsPrincipal.Claims.ToList();
//it it doesn't contains the Role claims, add a role claims
if (!claimresult.Any(c => c.Type == ClaimTypes.Role))
{
//add claims to current user.
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Role, userrole));
}
//refresh the Login
await _signInManager.RefreshSignInAsync(user);
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}

Based on the answer from the Lik Now I have added the below code in my login page and it's working fine.
var claims = new List<Claim>()
{
new Claim("UserId", createdUser?.Id),
new Claim("UserName", createdUser?.UserName),
new Claim("OrganizationId", createdUser?.OrganizationId),
new Claim("OrganizationName", OrganizationName)
};
var userRoles = await _userManager.GetRolesAsync(createdUser);
if (userRoles != null && userRoles.Count > 0)
{
foreach (var role in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
}
var claimsPrincipal = await _signInManager.CreateUserPrincipalAsync(createdUser);
var claimResult = claimsPrincipal.Claims.ToList();
if (claims != null && claims.Count > 0)
{
await _userManager.AddClaimsAsync(createdUser, claims.ToList());
}
await _signInManager.RefreshSignInAsync(createdUser);

Related

Authorization with windows authentication in ASP.NET Core 3.1

As I understand there is a way to retrieve group where user belong.
For example Admins, Users etc.
After doing that I want to transform this into claims. I cannot find how I can retrieve a user's groups.
Currently I am using my local user and not (Domain Active Directory).
Is there any solutions for that issue?
Is it a good approach or it is better to retrieve permissions for each user from the database and then operate with them?
you need to know is that AD is working on windows host only.
read Microsoft docs before you start project
add startup configuration , create login page
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Login";
});
.....
app.UseAuthentication();
app.UseAuthorization();
third your needs:
your AD domain : yourdomain.com
your ldap url example: LDAP://DC.yourdomain.com
some knowledge about ldap query string
application.json config:
"ldap": {
"path": "LDAP://DC.yourdomain.com",
"domain": "yourdomain.com",
"personFilter": "(SAMAccountName={username})",
"groupFilter": "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))",
//"property": "cn,displayName,mail,givenName,sn,o,title,company,department,telephoneNumber,description,userPrincipalName",
"property": "cn,displayName,mail,givenName,sn,o,title,company,department"
//"property": "cn,member,memberof,sAMAccountName,primaryGroupToken"
}
and you can use this method to check username and password
public Dictionary<string, string> UserInfo { get; private set; }
public bool IsAuthenticated(string username, string password)
{
bool result = false;
string domainAndUsername = Configuration["ldap:domain"] + #"\" + username;
DirectoryEntry entry = new DirectoryEntry(Configuration["ldap:path"], domainAndUsername, password);
try
{
Object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = Configuration["ldap:personFilter"].Replace("{username}", username);
var propList = Configuration["ldap:property"].Split(',');
search.PropertiesToLoad.AddRange(propList);
SearchResult searchResult = search.FindOne();
if (null == searchResult)
{
return false;
}
foreach (var propName in propList)
{
UserInfo.Add(propName, GetProperty(searchResult.Properties, propName));
}
DirectoryEntry obUser = new DirectoryEntry(searchResult.Path);
object obGroups = obUser.Invoke("Groups");
var groupList = new List<string>();
foreach (object ob in (IEnumerable)obGroups)
{
DirectoryEntry obGpEntry = new DirectoryEntry(ob);
groupList.Add(obGpEntry.Name);
}
UserInfo.Add("group", string.Join(",", groupList));
result = true;
}
catch (Exception ex)
{
throw new SysAuthException("Invalid Authentication", ex);
}
return result;
}
when login success you can check all user info from userInfo property
sample code for login page (add claim and login state to net core pipeline):
try
{
if (Authentication.IsAuthenticated(UserData.Username, UserData.Password))
{
var claims = new List<Claim>() {
new Claim(ClaimTypes.Name, UserData.Username)
};
foreach (var item in Authentication.UserInfo)
{
claims.Add(new Claim(item.Key, item.Value));
}
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
{
IsPersistent = UserData.RememberLogin
});
if (!string.IsNullOrEmpty(UserData.ReturnUrl))
return LocalRedirect(UserData.ReturnUrl);
return Redirect("/");
}
}
catch (SysAuthException ex)
{
Error = ex.InnerException.Message;
}
if you need to protect your page add #attribute [Authorize] in top of your page also you can check other claim for example roles or group with this attribute
sample code show current user info
<div>
<table class="table table-bordered table-striped">
<caption>Current User Info</caption>
#foreach (var claim in User.Claims)
{
<tr><td>#claim.Type</td><td>#claim.Value</td></tr>
}
</table>
</div>

Custom claim not refreshed

Custom claims set is not updating. I have two API and an MVC project API1 validates the user and set claims, API2 is supposed to consume the custom claims, MVC is the UI.
The below code is from API1 that runs successfully. The claims are added. I can see the same in API2, (but it's now visible anywhere else including the MVC. maybe I don't know how to access it).
But the main problem is not that, the main problem is, the claims are not updated when it's changed the next time through API1. So, even if another user login in the claim still shows the first users' claims.
public async Task<AuthenticationModel> GetTokenAsync(LoginRequestModel model)
{
//...
if (await _userManager.CheckPasswordAsync(user, model.Password))
{
JwtSecurityToken jwtSecurityToken = await CreateJwtToken(user, model.Database);
authenticationModel.Token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
}
private async Task<JwtSecurityToken> CreateJwtToken(ApplicationUser user, string myClaimValue)
{
var userClaims = await _userManager.GetClaimsAsync(user);
var roles = await _userManager.GetRolesAsync(user);
var roleClaims = new List<Claim>();
for (int i = 0; i < roles.Count; i++)
{
roleClaims.Add(new Claim("roles", roles[i]));
}
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim("uid", user.Id),
new Claim("MyCustomClaim", myClaimValue)
}
.Union(userClaims)
.Union(roleClaims);
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key));
var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
var jwtSecurityToken = new JwtSecurityToken(
issuer: _jwt.Issuer,
audience: _jwt.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(_jwt.DurationInMinutes),
signingCredentials: signingCredentials);
return jwtSecurityToken;
}
This data is used in the following code. It works for the first time, but when the values are changed in API1, the same is not reflected in the claims. The claim always shows the first value assigned to it. I tried getting details of the claim in the MVC for debug and initialization, but everywhere else the claims are empty.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var identity = _httpContextAccessor.HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
var myclaim= identity.FindFirst("MyCustomClaim").Value;
}
optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
}

BlazorWASM: Email disappears on DisplayLogin

I am trying to do JWT Authentication, I have BlazorWASM (asp.net core hosted)
I have created a CustomAuthenticationStateProvider.
After logging in the user, I got this as expected:
But after re-rendering the use name disappears, but still I have access to authorised resources
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
var userInfo = await _localStorage.GetItemAsync<LoginResponse>("UserInfo");
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
Notice that I am only storing the token.
And this is the Login Method
public async Task<Response<LoginResponse>> Login(LoginRequest loginModel)
{
var loginAsJson = JsonSerializer.Serialize(loginModel);
var response = await _httpClient.PostAsync("api/Account/Login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
var loginResult = JsonSerializer.Deserialize<Response<LoginResponse>>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (!response.IsSuccessStatusCode)
{
return loginResult;
}
await _localStorage.SetItemAsync("authToken", loginResult.Data.JWToken);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(loginModel.Email);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Data.JWToken);
return loginResult;
}
to be more specific, call GetAuthenticationStateAsync cause the same result (username disappears).
So what should I do?

how to include the role to the JWT token returning?

What I have in my mind when navigating through the application, I want to save the token to the localhost along with role name and I will check if the users have access to a certain link. Is that how it works? with Authgard in Angular 8?. Can you give me some insight of navigating an application with the role from Identity(which is built in from ASP.net core 3.1).
login
// POST api/auth/login
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsViewModel credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null)
{
//return null;
return BadRequest(Error.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, credentials.UserName, _jwtOptions, new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
Generate Token Method
public static async Task<string> GenerateJwt(ClaimsIdentity identity, IJwtFactory jwtFactory, string userName, JwtIssuerOptions jwtOptions, JsonSerializerSettings serializerSettings)
{
var response = new
{
id = identity.Claims.Single(c => c.Type == "id").Value,
//probably here I want to send the role too!!
auth_token = await jwtFactory.GenerateEncodedToken(userName, identity),
expires_in = (int)jwtOptions.ValidFor.TotalSeconds
};
return JsonConvert.SerializeObject(response, serializerSettings);
}
}
You need to add claims information when generating your JWT.
Here`s an example
And another one:
1 part(how to implement JWT), 2 part(about claims here)

itfoxtex saml mvccore, attribute replace NameID

I cannot figure out how to get an attribute from the the saml response in place of the NameID value.
My IDP team is returning the value I need in an attribute rather than in NameID(which they wont budge on).
Thanks for any help!
I am running MVC Core. I have everything setup and running for NameID from the example 'TestWebAppCore' for ITfoxtec.Identity.Saml2.
I am trying to get this value in place of NameID for the session username:
saml:AttributeStatement>
<saml:Attribute Name="valueName"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
>
<saml:AttributeValue>IDValue</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
[Route("AssertionConsumerService")]
public async Task<IActionResult> AssertionConsumerService()
{
var binding = new Saml2PostBinding();
var saml2AuthnResponse = new Saml2AuthnResponse(config);
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success) {
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
}
binding.Unbind(Request.ToGenericHttpRequest(),
saml2AuthnResponse);
try {
await saml2AuthnResponse.CreateSession(HttpContext,
claimsTransform: (claimsPrincipal) =>
ClaimsTransform.Transform(claimsPrincipal));
}
catch (Exception ex) {
log.writeLog(ex.Message.ToString());
}
var relayStateQuery = binding.GetRelayStateQuery();
var returnUrl = relayStateQuery.ContainsKey(relayStateReturnUrl)
? relayStateQuery[relayStateReturnUrl] : Url.Content("~/");
return Redirect(returnUrl);
}
It is probably not possible to logout without the NameID but you can login without.
In .NET the NameID is translated into the ClaimTypes.NameIdentifier claim. The users claims is handled in the ClaimsTransform.CreateClaimsPrincipal method.
You can either translate the incoming custom claim "valueName" to a ClaimTypes.NameIdentifier claim:
private static ClaimsPrincipal CreateClaimsPrincipal(ClaimsPrincipal incomingPrincipal)
{
var claims = new List<Claim>();
claims.AddRange(GetSaml2LogoutClaims(incomingPrincipal));
claims.Add(new Claim(ClaimTypes.NameIdentifier, GetClaimValue(incomingPrincipal, "valueName")));
return new ClaimsPrincipal(new ClaimsIdentity(claims, incomingPrincipal.Identity.AuthenticationType, ClaimTypes.NameIdentifier, ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)incomingPrincipal.Identity).BootstrapContext
});
}
Or change the identity claim in the ClaimsIdentity to the incoming custom claim "valueName":
private static ClaimsPrincipal CreateClaimsPrincipal(ClaimsPrincipal incomingPrincipal)
{
var claims = new List<Claim>();
// All claims
claims.AddRange(incomingPrincipal.Claims);
return new ClaimsPrincipal(new ClaimsIdentity(claims, incomingPrincipal.Identity.AuthenticationType, "valueName", ClaimTypes.Role)
{
BootstrapContext = ((ClaimsIdentity)incomingPrincipal.Identity).BootstrapContext
});
}