My Jwt token is built with additional claims that when used are not present.
#region Helper Methods
private async Task<JwtSecurityToken> GenerateToken(ApplicationUser user)
{
var roles = await UserManager.GetRolesAsync(user);
try
{
var claims = new List<Claim>
{
new Claim("UserName", (string) user.UserName),
new Claim("Email", user.Email),
new Claim("Phone", user.PhoneNumber ?? ""),
new Claim("ChurchId", user.ChurchId.ToString()),
new Claim("FirstName", user.FirstName),
new Claim("LastName", user.LastName),
new Claim("FullName", $"{user.FirstName} {user.LastName}"),
new Claim("TwoFactorEnabled", user.TwoFactorEnabled.ToString()),
new Claim("IsEmail", user.IsEmail.ToString()),
new Claim("IsText", user.IsText.ToString()),
new Claim("Id", user.Id.ToString()),
new Claim("TwoFactorExpires", user.Expires2FA.ToString())
};
if (user._2FA != null)
{
claims.Add(new Claim("TwoFactorCode", user._2FA.ToString()));
}
else
{
claims.Add(new Claim("TwoFactorCode", string.Empty));
}
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSecurityKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expiry = DateTime.Now.AddDays(Convert.ToInt32(_configuration["JwtExpiryInDays"]));
var token = new JwtSecurityToken(
_configuration["JwtIssuer"],
_configuration["JwtAudience"],
claims,
expires: expiry,
signingCredentials: creds
);
return token;
}
catch (Exception ex)
{
throw;
}
}
The generated token looks good as validated at Jwt.Io There are 14 claims.
When the token is set as the bearer token and used only primary claims exist. There are now only 5 claims.
I believe this is something simple but apparently, I'm code-blind currently.
Related
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);
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);
}
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?
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
});
}
Update
some code on web side
startup.cs
app.UseOAuthAuthentication(new OAuthOptions()
{
AuthenticationScheme = "Microsoft-AccessToken",
DisplayName = "MicrosoftAccount-AccessToken",
ClientId = {CliendID},
ClientSecret = {ClientSecret},
CallbackPath = new PathString("/signin-microsoft-token"),
AuthorizationEndpoint = MicrosoftAccountDefaults.AuthorizationEndpoint,
TokenEndpoint = MicrosoftAccountDefaults.TokenEndpoint,
UserInformationEndpoint = MicrosoftAccountDefaults.UserInformationEndpoint,
Scope = { "https://graph.microsoft.com/user.read" },
SaveTokens = true,
Events = new OAuthEvents()
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
var identifier = user.Value<string>("id");
if (!string.IsNullOrEmpty(identifier))
{
context.Identity.AddClaim(new Claim(
ClaimTypes.NameIdentifier, identifier,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var userName = user.Value<string>("displayName");
if (!string.IsNullOrEmpty(userName))
{
context.Identity.AddClaim(new Claim(
ClaimTypes.Name, userName,
ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var email = user.Value<string>("userPrincipalName");
if (!string.IsNullOrEmpty(email))
{
context.Identity.AddClaim(new Claim(
ClaimTypes.Email, email,
ClaimValueTypes.Email, context.Options.ClaimsIssuer));
}
}
}
});
HomeController.cs
[Authorize]
public string GetInfo()
{
return "Hello world!";
}
I am able to retrieve user's token with code like this
string MicrosoftClientID = {ClientID};
string MicrosoftCallbackURL = "urn:ietf:wg:oauth:2.0:oob";
string scope = WebUtility.UrlEncode("openid offline_access https://graph.microsoft.com/user.read");
string MicrosoftURL = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=" + MicrosoftClientID + "&response_type=code&redirect_uri=" + MicrosoftCallbackURL + "&response_mode=query&scope=" + scope;
Uri StartUri = new Uri(MicrosoftURL);
Uri EndUri = new Uri(MicrosoftCallbackURL);
WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
WebAuthenticationOptions.None,
StartUri,
EndUri);
if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
string code = WebAuthenticationResult.ResponseData.Replace("urn:ietf:wg:oauth:2.0:oob?code=", "");
string strContent = "client_id=" + MicrosoftClientID + "&scope=" + scope + "&code=" + code + "&redirect_uri=" + MicrosoftCallbackURL + "&grant_type=authorization_code";
HttpClient httpClient = new HttpClient();
HttpContent httpContent = new StringContent(strContent);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage httpResponseMessage = await httpClient.PostAsync("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", httpContent);
string stringResponse = await httpResponseMessage.Content.ReadAsStringAsync();
}
but how can i use the token to make a request to API of my .NET Core Web application, which is hosted on azure?
I have tried these
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationModel.AccessToken);
string apicontent = await httpClient.GetStringAsync("https://{host}.azurewebsites.net/home/GetInfo");
apicontent.ToString();
all I got is html of login page
Any help please?
This Authorize is as same as an normal web application.
If you don't want to authenticate the user when you call this API, you can remove the attribure [Authorize] for the GetInfo. And if you also have [Authorize] attribure for the controller, you can add [AllowAnonymous] attribute to specify this action is skipped by AuthorizeAttribute during authorization.
You can refer to here about Authentication and Authorization.
Let's start from the Web Api:
Here is my sample method to retrieve friends list from the Api.
Note that there is "Route prefix" attribute - to indicate which resource I would like to retrieve and "Authorize" attribute to demand authentication before calling this method:
[RoutePrefix("api/Friends")]
public class FriendsController : ApiController
{
[Authorize]
[Route("GetConfirmedFriends")]
public IHttpActionResult GetConfirmedFriends()
{
using (DbWrapper dbContext = new DbWrapper())
{
return Ok(dbContext.GetConfirmedFriends());
}
}
}
Now in the UWP application you will call this method like below:
public async Task<ObservableCollection<Friend>> GetConfirmedFriends()
{
//access token eretrieved after MS authentication:
if (_accessToken == null)
throw new NullReferenceException("Access token cannot be empty. Please authenticate first");
try
{
using (HttpClient client = new HttpClient())
{
ObservableCollection<Friend> friends = null;
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _accessToken);
var data = await client.GetAsync(string.Concat(https://{host}.azurewebsites.net/, "api/Friends/GetConfirmedFriends"));
var jsonResponse = await data.Content.ReadAsStringAsync();
if (jsonResponse != null)
friends = JsonConvert.DeserializeObject<ObservableCollection<Friend>>(jsonResponse);
return friends;
}
}
catch (WebException exception)
{
throw new WebException("An error has occurred while calling GetConfirmedFriends method: " + exception.Message);
}
}
Please check and let me know.