using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; namespace HRD.LDAPService.JWT { public class JwtManager { private const string GlbExtendedAttributes = "ExtendedAttributes_"; private readonly LdapAuthenticationService _ldapAuthService; public JwtManager(LdapAuthenticationService ldapAuthService) { _ldapAuthService = ldapAuthService; } public static LdapUser DecryptTokenAsLdapUser(string token) { if (string.IsNullOrEmpty(token)) { return default; } //Check token with "Bearer" prefix if (token.StartsWith("Bearer", StringComparison.InvariantCultureIgnoreCase)) { token = token.Split(" ").Last(); } if (string.IsNullOrEmpty(token)) { return default; } JwtSecurityToken jwtSecurityToken = DecryptToken(token); if (jwtSecurityToken == null) { return default; } LdapUser ldapUser = ClaimsIdentityToLdapUser(jwtSecurityToken.Claims.ToList()); if (ldapUser == null) { return default; } ldapUser.Token = token; return ldapUser; } public static JwtSecurityToken DecryptToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(JwtTokenConfig.Secret); try { tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, ClockSkew = TimeSpan.FromSeconds(60) // set clockskew to zero so tokens expire exactly at token expiration time }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; return jwtToken; } //IDX10223: Lifetime validation failed. The token is expired. ValidTo: 'System.DateTime', Current time: 'System.DateTime'. catch (SecurityTokenExpiredException ex) { throw ex; } catch (Exception ex) { // return null if validation fails throw ex; } } public LdapUser RenewLdapUserWithJwtToken(string token) { LdapUser renewLdapUser = null; try { if (string.IsNullOrEmpty(token)) { throw new ArgumentNullException($"Token is missing!"); } renewLdapUser = _ldapAuthService.RenewIdentity(token); if (renewLdapUser is null) { throw new Exception($"Can't renew from token!"); } if (!renewLdapUser.IsValidatCredentials) { throw new Exception($"Invalid credentials!"); } if (!renewLdapUser.Enabled) { throw new Exception($"Ldap-User is disabled!"); } (string newtoken, DateTime newExpiredOn) = CreateToken(renewLdapUser); renewLdapUser.Token = newtoken; renewLdapUser.JwtExpiredOn = newExpiredOn; if (!renewLdapUser.IsValid()) { throw new Exception($"Ldapuser is not valid!"); } return renewLdapUser; } catch (Exception ex) { throw ex; } } public LdapUser RenewLdapUserWithJwtToken(LdapUser ldapUser) { LdapUser renewLdapUser = null; try { if (string.IsNullOrEmpty(ldapUser?.Token)) { throw new Exception($"Token is missing (Login:{ldapUser.LoginName})"); } renewLdapUser = _ldapAuthService.RenewIdentity(ldapUser); if (renewLdapUser is null) { return default; } //if (!ldapUser.IsValidatCredentials) //{ // ldapUser.Token = string.Empty; // return false; //} if (!renewLdapUser.Enabled) { renewLdapUser.Token = string.Empty; return renewLdapUser; } var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(JwtTokenConfig.Secret); var claims = CreateClaimsIdentity(renewLdapUser); var tokenDescriptor = new SecurityTokenDescriptor { Subject = claims, Expires = DateTime.UtcNow.AddMinutes(JwtTokenConfig.ExpirationInMin), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); ldapUser.Token = tokenHandler.WriteToken(token); ldapUser.JwtExpiredOn = token.ValidTo; if (renewLdapUser.IsValid()) { return renewLdapUser; } return default; } catch (Exception ex) { throw ex; } } public bool GenerateLdapUserWithJwtToken(LdapUser ldapUser) { try { if (!_ldapAuthService.CheckAndUpdateIdentityWithPassword(ldapUser)) { return false; } if (!ldapUser.IsValidatCredentials) { ldapUser.Token = string.Empty; return false; } if (!ldapUser.Enabled) { ldapUser.Token = string.Empty; return false; } (string token, DateTime jwtExpiredOn) = CreateToken(ldapUser); ldapUser.Token = token; ldapUser.JwtExpiredOn = jwtExpiredOn; return ldapUser.IsValid(); } catch (Exception ex) { throw ex; } } public static string GenerateHash(string password) { return JWTCrypt.SHA512(password); } private static (string, DateTime) CreateToken(LdapUser ldapUser) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(JwtTokenConfig.Secret); var claims = CreateClaimsIdentity(ldapUser); var tokenDescriptor = new SecurityTokenDescriptor { Subject = claims, Expires = DateTime.UtcNow.AddMinutes(JwtTokenConfig.ExpirationInMin), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return (tokenHandler.WriteToken(token), token.ValidTo); } public static bool IsValidatJwtTokenSubject(string token) { if (string.IsNullOrEmpty(token)) { return false; } token = token.Trim(); if (token.IndexOf(" ", StringComparison.InvariantCultureIgnoreCase) > 0) { if (token.StartsWith("Bearer", StringComparison.InvariantCultureIgnoreCase)) //token with "Bearer" prefix { token = token.Split(" ").Last(); } else { token = token.Split(" ").First(); } } try { var jwtToken = DecryptToken(token); return !String.IsNullOrEmpty(jwtToken?.Subject); //Loginname } catch (Exception ex) { throw ex; } } private static LdapUser ClaimsIdentityToLdapUser(List claims) { LdapUser user = new LdapUser(""); foreach (var claim in claims) { switch (claim.Type) { case JwtRegisteredClaimNames.Sub: user.LoginName = claim.Value; break; case JwtRegisteredClaimNames.Email: user.Email = claim.Value; break; case JwtRegisteredClaimNames.NameId: { if (int.TryParse(claim.Value, out int id)) { user.UserId = id; } } break; case JwtRegisteredClaimNames.Jti: { if (Guid.TryParse(claim.Value, out Guid g)) { user.LdapGuid = g; } } break; case JwtGlobals.CLAIM_DEPARTNENTID: user.DepartmentId = int.Parse(claim.Value); break; case JwtGlobals.CLAIM_EXTENDETDEPARTNENTIDLIST: user.ExtendedDepartmentIdList = claim.Value; break; case JwtGlobals.CLAIM_ROLE: user.AddRole(claim.Value); break; case JwtRegisteredClaimNames.Exp: { //#pragma warning disable CA1305 // Specify IFormatProvider var expValue = Convert.ToInt32(claim.Value); //#pragma warning restore CA1305 // Specify IFormatProvider DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(expValue); user.JwtExpiredOn = dateTimeOffset.UtcDateTime; } break; case var s when claim.Type.StartsWith(GlbExtendedAttributes): var strKey = claim.Type.Substring(GlbExtendedAttributes.Length, claim.Type.Length - GlbExtendedAttributes.Length); user.ExtendedAttributesList.Add(new KeyValuePair(strKey, claim.Value)); break; default: break; } } return user; } private static ClaimsIdentity CreateClaimsIdentity(LdapUser user) { ClaimsIdentity claimsIdentity = new ClaimsIdentity(); List claims = new List { CreateClaim(JwtRegisteredClaimNames.Sub, user.LoginName), CreateClaim(JwtRegisteredClaimNames.NameId, user.UserId), CreateClaim(JwtRegisteredClaimNames.Email, user.Email), CreateClaim(JwtGlobals.CLAIM_DEPARTNENTID, user.DepartmentId), CreateClaim(JwtGlobals.CLAIM_EXTENDETDEPARTNENTIDLIST, user.ExtendedDepartmentIdList) }; user.RoleList.ForEach(role => claims.Add( CreateClaim(ClaimTypes.Role, role.Role) )); user.ExtendedAttributesList.ForEach(item => claims.Add( CreateClaim($"{GlbExtendedAttributes}{item.Key}", item.Value) )); claimsIdentity.AddClaims(claims); return claimsIdentity; } private static Claim CreateClaim(string claimName, int claimValue) { return new Claim(claimName, string.IsNullOrEmpty($"{claimValue}") ? string.Empty : $"{claimValue}"); } private static Claim CreateClaim(string claimName, string claimValue) { return new Claim(claimName, string.IsNullOrEmpty(claimValue) ? string.Empty : claimValue); } } }