Blazor Server Add Claims after authentication - c#

Hi I want user to select branch to enter after login and user may have different role for each branch. Then this info will be added to user claims. Below is how I implemented IClaimsTransformation TransformAsync method.
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Clone current identity
var clone = principal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
// Support AD and local accounts
var nameId = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier ||
c.Type == ClaimTypes.Name);
if (nameId == null)
{
return principal;
}
// Get userInfo from database
UserBranchRoleModel userInfo = await Task.Run(() => _userService.GetCurrentBranchRole(nameId.Value));
if (userInfo?.User == null)
{
return principal;
}
List<Claim> claims = new()
{
new Claim(newIdentity.NameClaimType, userInfo.User.UserID),
new Claim("BranchID", userInfo.Branch.BranchID.ToString()),
new Claim("BranchName", userInfo.Branch.BranchName),
new Claim(newIdentity.RoleClaimType, userInfo.Role.RoleID)
};
newIdentity.AddClaims(claims);
return clone;
}
The problem is claims don't get updated if I do not refresh the page. Is there any way refresh claims without refreshing page altogether ?
Here is How I display claims in Index page
#foreach (var c in #user.Claims)
{
<div>#c.Type: #c.Value</div>
}
And this is how I Update DB
async Task changeBranch(string UserID)
{
DialogOptions options = new DialogOptions() { CloseButton = false };
DialogParameters parameters = new DialogParameters();
parameters.Add("UserID", UserID);
var dialog = DialogService.Show<ChooseBranch>("Select A Branch", parameters, options);
var result = await dialog.Result;
if (!result.Cancelled)
{
UserBranchRoleModel BranchRole = (UserBranchRoleModel)result.Data;
_user.SetCurrentBranchRole(BranchRole);
StateHasChanged();
}
}
}

Related

Revoke JWT using C#

I have a custom Authorize class that I use to invalidate the token when user requests data or anything from the server
but whenever the token expires the principle still returns IsAuthenticated as true and still calls the controllers and get data.
What I want it to do is to invalidate the token and explicitly logout the user out of the system. I couldn't find anything helpful. I can provide code of the JWT attribute/filters if needed
Update 1: Token Generation
public static string GenerateToken(User user)
{
int expireMinutes;
try
{
expireMinutes = string.IsNullOrEmpty(ConfigurationManager.AppSettings["SessionTimeInMinutes"])
? 30
: int.Parse(ConfigurationManager.AppSettings["SessionTimeInMinutes"]);
}
catch (Exception)
{
expireMinutes = 30;
}
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.Now;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Email, user.Email)
,new Claim(ClaimTypes.Name, user.FirstName + " " + user.LastName)
,new Claim("uid", user.Id.ToString())
,new Claim("cid", user.ClientId.ToString())
,new Claim("rid", string.Join(",", user.Roles.Select(r => r.RoleId).ToList()))
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
IssuedAt = now,
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
Server side Authorization Token
public async Task AuthenticateAsync(
HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var excludedList = new List<string>();
excludedList.Add("/api/Security/IsMustChangePassword");
excludedList.Add("/api/Security/IsTwoFactorEnabled");
if (!excludedList.Contains(context.ActionContext.Request.RequestUri.LocalPath))
{
var request = context.Request;
var authorization = request.Headers.Authorization;
if (authorization == null || authorization.Scheme != "Token")
{
return;
}
if (string.IsNullOrEmpty(authorization.Parameter))
{
context.ErrorResult = new AuthenticationFailureResult("Missing Jwt Token", request);
return;
}
//{
// context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
// return;
//}
var token = authorization.Parameter;
var principal = await AuthenticateJwtToken(token).ConfigureAwait(true);
var userId = int.Parse(new JwtManager().GetUserIdFromToken(token));
var accountManager = new AccountManager();
var user = accountManager.GetUserDetails(userId);
var newToken = JwtManager.GenerateToken(user);
if (principal == null)
context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
else
context.Principal = principal;
if (principal.Identity.IsAuthenticated)
{
var expiryDate = JwtManager.GetSecurityToken(token).ValidTo.ToLocalTime();
if ((DateTime.Now - expiryDate).TotalSeconds > 0)
{
context.Request.Headers.Authorization = null;
context.Request.RequestUri = null;
}
else
{
var authorize = new AuthenticationHeaderValue("token", newToken);
context.Request.Headers.Authorization = authorize;
context.ActionContext.Request.Headers.Authorization = authorization;
}
}
}
}
private static bool ValidateToken(string token, out string username, out
string passwordHash)
{
username = null;
passwordHash = null;
try
{
var principle = JwtManager.GetPrincipal(token);
var identity = principle.Identity as ClaimsIdentity;
if (identity == null)
return false;
if (!identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
var passwordClaim = identity.FindFirst(ClaimTypes.Hash);
username = usernameClaim?.Value;
passwordHash = passwordClaim?.Value;
return !string.IsNullOrEmpty(username);
var user = identity.FindFirst(username);
return (user != null);
//return (user != null && user.PasswordHash == passwordHash);
}
catch (NullReferenceException)
{
return false;
}
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
string passwordHash;
if (!ValidateToken(token, out username, out passwordHash))
return Task.FromResult<IPrincipal>(null);
// based on username to get more information from database in order to build local identity
var claims = new List<Claim> { new Claim(ClaimTypes.Name, username) };
//claims.Add(new Claim(ClaimTypes.Hash, passwordHash));
// Add more claims if needed: Roles, ...
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken)
{
var authorization = context.Request.Headers.Authorization;
var excludedList =
new List<string> {
"/api/Security/IsMustChangePassword",
"/api/Security/IsTwoFactorEnabled" };
if (context.Request.Headers.Authorization != null)
{
if (!excludedList.Contains(context.ActionContext.Request.RequestUri.LocalPath))
{
var token = context.Request.Headers.Authorization.Parameter;
var userId = int.Parse(new JwtManager().GetUserIdFromToken(token));
var accountManager = new AccountManager();
var user = accountManager.GetUserDetails(userId);
var newToken = JwtManager.GenerateToken(user);
var expiryDate = JwtManager.GetSecurityToken(token).ValidTo.ToLocalTime();
if ((DateTime.Now - expiryDate).TotalSeconds > 0)
{
context.Request.Headers.Authorization = null;
context.Request.RequestUri = null;
context.Request.RequestUri = new Uri("/Login");
}
else
{
var authorize = new AuthenticationHeaderValue("token", newToken);
context.Request.Headers.Authorization = authorize;
context.ActionContext.Request.Headers.Authorization = authorization;
}
Challenge(context);
}
}
else
{
var req = context.Request.RequestUri;
var url = context.Request.RequestUri = new Uri($"http://{req.Host}:{req.Port}/api/Security/login");
context.Request.RequestUri = url;
context.Request.Headers.Authorization = null;
context.Result= new AuthenticationFailureResult(string.Empty, new HttpRequestMessage());
}
return Task.FromResult(0);
}
First of all: A JWT is just a client-token the client can manipulate. There is no absolute security here. The JWT is secured by the symmetirc key but cannot be invalidated itself. A valid token remains valid until it expires. It's a flaw of JWT (as #TimBiegeleisen pointed out in the comments) that a token itself cannot be easily invalidated.
If a user works too long and gets logged out automatically, your JWT has expired and everthing's good. There's no hassle as it runs out naturally and you have no need to act.
To invalidate a token you need to create a new token with Expires = now.AddMinutes(-1). That way the next time you check the JWT next time you see it has expired.
The case that a user may save a JWT and use it even after being logged out can only be come by with blacklisting JWTs or maintaining some other kind of serverside session aditionally (which doesn't work i.e. with a stateless webservice).
EDIT: Removed wrong information and added a additional way to invalidate a JWT(blacklisting)
You can simply cash a the token you want to revoke
and then make your authentication part compare request that has the revoked
with the one in the cash and refuse the request based on that
until the token expires , the cash should know the remining time for the token and cash it for that long

GetClaimsAsync(user) throws Sequence contains more than one matching element

I have a .NET Core 2.0 Web API. I am using Jwt authentication on it.
Whenever the application reaches this line, an exception is thrown:
var userClaims = await _userManager.GetClaimsAsync(user);
The exception is:
System.InvalidOperationException: Sequence contains more than one matching element
What is weird is that this wasn't happening before, it started happening when I upgraded to .NET Core 2.0 from 1.1.
The Seed method for the database is below:
public async Task Seed()
{
var adminUserJ = await _userManager.FindByNameAsync("Ciwan");
var regularUser = await _userManager.FindByNameAsync("John");
if (adminUserJ == null)
{
if (!await _roleManager.RoleExistsAsync("Admin"))
{
var role = new IdentityRole("Admin");
role.Claims.Add(new IdentityRoleClaim<string> { ClaimType = "IsAdmin", ClaimValue = "True" });
await _roleManager.CreateAsync(role);
}
adminUserJ = new BbUser
{
UserName = "Ciwan",
Email = "ck83s9#gmail.com"
};
var userResult = await _userManager.CreateAsync(adminUserJ, "Welcome123");
var roleResult = await _userManager.AddToRoleAsync(adminUserJ, "Admin");
var claimResult = await _userManager.AddClaimAsync(adminUserJ, new Claim("Points", "10"));
if (!userResult.Succeeded || !roleResult.Succeeded || !claimResult.Succeeded)
{
throw new InvalidOperationException("Failed to build user and roles");
}
}
if (regularUser == null)
{
if (!await _roleManager.RoleExistsAsync("Regular"))
{
var role = new IdentityRole("Regular");
role.Claims.Add(new IdentityRoleClaim<string> { ClaimType = "IsRegular", ClaimValue = "True" });
await _roleManager.CreateAsync(role);
}
regularUser = new BbUser
{
UserName = "John",
Email = "j.watson#world.com"
};
var userResult = await _userManager.CreateAsync(regularUser, "BigWow321");
var roleResult = await _userManager.AddToRoleAsync(regularUser, "Regular");
var claimResult = await _userManager.AddClaimAsync(regularUser, new Claim("Points", "10"));
if (!userResult.Succeeded || !roleResult.Succeeded || !claimResult.Succeeded)
{
throw new InvalidOperationException("Failed to build user and roles");
}
}
_context.AddRange(GetListOfArtists(adminUserJ.Id, regularUser.Id));
await _context.SaveChangesAsync();
}
I can't see anything wrong. I tried looking at the AspNetUserClaims table in the database, but all seems OK. I have 2 claims in there, one for each user.
This error happens when I attempt to log in a user, so the request arrives here:
[HttpPost("auth/token")]
public async Task<IActionResult> CreateToken([FromBody] CredentialsDto credentials)
{
try
{
var user = await _userManager.FindByNameAsync(credentials.Username);
if (user != null)
{
if (IsUserPasswordValid(credentials, user))
{
var claims = await GetClaimsAsync(user);
var token = CreateNewJwtToken(claims);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
return BadRequest("Failed to generate token!");
}
private async Task<IEnumerable<Claim>> GetClaimsAsync(BbUser user)
{
var userClaims = await _userManager.GetClaimsAsync(user);
return new[] {
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
}.Union(userClaims);
}

Seeding admin database Role not adding

I am having some issues with getting my admin user have an admin role. Once I create a user and it succeeds I tried adding the admin role with AddToRoleAsync. But I am not sure why it's not being added. I then tried to have a check so that if the admin has no admin role it would add it. It looks like the command executes but I don't see it added to the database.
This is my code:
public async Task CreateAdmin()
{
// Add roles
string[] roles = new string[] {"Administrator", "User"};
foreach(string role in roles)
{
bool result = await _roleManager.RoleExistsAsync(role);
if(!result)
{
await _roleManager.CreateAsync(new IdentityRole(role));
}
}
// Add admin user
if (!_context.Users.Any(u => u.UserName == "Admin"))
{
var user = new Users
{
Email="admin#admin.com",
NormalizedEmail="ADMIN#ADMIN.COM",
UserName="admin#admin.com",
NormalizedUserName="ADMIN",
EmailConfirmed = true,
};
var result = await _userManager.CreateAsync(user, "Password123");
}
var adminuser = await _userManager.FindByEmailAsync("admin#admin.com");
bool flag = await _userManager.IsInRoleAsync(adminuser, "Administrator");
if(!flag)
{
var role = await _roleManager.FindByNameAsync("Administrator");
await _userManager.AddToRoleAsync(adminuser, "Administrator");
}
}
If you want the full DbIntilizer or more code let me know.
Anyone know what I am doing wrong?
Edit
Reworked it to follow this
http://www.locktar.nl/programming/net-core/seed-database-users-roles-dotnet-core-2-0-ef/
and now it works.
This is how i do it.
public async Task InitializeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
// Create DB
serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
// Add roles
var roleManager = serviceScope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole<long>>>();
if (!roleManager.Roles.Any())
{
foreach (var role in Config.GetTestRolls())
{
await roleManager.CreateAsync(role);
}
}
// Add users
var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
if (userManager.Users.Any()) return;
foreach (var user in Config.GetTestUsers())
{
await userManager.CreateAsync(user, "Password123!");
}
// find first user add to first role (hack)
var adminUser = await userManager.FindByEmailAsync(Config.GetTestUsers().FirstOrDefault()?.Email);
if (adminUser != null)
{
await userManager.AddToRoleAsync(adminUser, Config.GetTestRolls().FirstOrDefault()?.Name);
}
}
Code ripped from my GitHub Project found here

ASP.NET MVC 5 get claims

I use third-party auth nuget instagram package for login and set new claim:
app.UseInstagramAuthentication(new InstagramAuthenticationOptions
{
ClientId = "XXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXX",
Provider = new InstagramAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn::instagram::accesstoken", context.AccessToken));
return Task.FromResult(0);
}
}
but when I try to get this claim
var ctx = HttpContext.GetOwinContext();
ClaimsPrincipal user = ctx.Authentication.User;
IEnumerable<Claim> claims = user.Claims;
This claim does not exist in the list. Why?
You need to retrieve and store those claims on external login, maybe something like:
private async Task StoreAuthTokenClaims(ApplicationUser user)
{
// Get the claims identity
ClaimsIdentity claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (claimsIdentity != null)
{
// Retrieve the existing claims
var currentClaims = await UserManager.GetClaimsAsync(user.Id);
// Get the list of access token related claims from the identity
var tokenClaims = claimsIdentity.Claims
.Where(c => c.Type.StartsWith("urn:tokens:"));
// Save the access token related claims
foreach (var tokenClaim in tokenClaims)
{
if (!currentClaims.Contains(tokenClaim))
{
await UserManager.AddClaimAsync(user.Id, tokenClaim);
}
}
}
}
And on the ExternalLoginConfirmation method:
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await StoreAuthTokenClaims(user);
// Sign in and redirect the user
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
After that, you can retrieve claims like:
var claimsIdentity = HttpContext.User.Identity as ClaimsIdentity;
if (claimsIdentity != null)
{
var claims = claimsIdentity.Claims;
}

Update Claims after user login

Is it possible to update claims after user login? I have frontend and admin panel in my site, basically I'm using claims to achieve this. If a user is logged in as Susan in frontend, I did something like this in my code :
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
userIdentity.AddClaim(new Claim("Id", this.UserName));
... //other claims here
So when user finally login on admin panel (when still logged on in frontend), I just want to add more claims for example :
userIdentity.AddClaim(new Claim("BackEndId", this.UserName));
How to achieve this?
To be able to read the claim on a cookie you need to sign user our and sign them back in again during the same request:
idenityt.AddClaim(new Claim("myClaimType", "myClaimValue"));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, idenityt);
Add function to ApplicationSignInManager class in Identity.Config.cs
public override Task SignInAsync(ApplicationUser user, bool isPersistent, bool rememberBrowser)
{
var claims = new List<Claim>()
{
new Claim("Id", this.UserName),
new Claim("BackEndId", this.UserName)
};
this.AuthenticationManager.User.AddIdentity(new ClaimsIdentity(claims));
return base.SignInAsync(user, isPersistent, rememberBrowser);
}
You can use this code to access claims.
private ClaimsPrincipal GetCurrentUser()
{
var context = HttpContext.GetOwinContext();
if (context == null)
return null;
if (context.Authentication == null || context.Authentication.User == null)
return null;
return context.Authentication.User;
}
private string GetUserId()
{
var user = GetCurrentUser();
if (user == null)
return null;
var claim = user.Claims.FirstOrDefault(o => o.Type == "Id");
if (claim == null)
return null;
return claim.Value;
}
private string GetBackEndId()
{
var user = GetCurrentUser();
if (user == null)
return null;
var claim = user.Claims.FirstOrDefault(o => o.Type == "BackEndId");
if (claim == null)
return null;
return claim.Value;
}

Categories