Hot Chocolate GraphQL Custom Authentication Series Using Pure Code First Technique - Part2 - Generating JWT(JSON Web Token) Access Token
Part1 discussed user registration. In this article, we are going to implement logic to generate the JWT access token in the Hot Chocolate GraphQL.
Overview On JWT(JSON Web Token):
JSON Web Token is a digitally signed and secured token for user validation. The jwt is constructed with 3 informative parts:
- Header
- Payload
- Signature
Install JWT NuGet:
Package Manager Command:
Install-Package System.IdentityModel.Tokens.Jwt -Version 6.9.0
.Net CLI Command:
dotnet add package System.IdentityModel.Tokens.Jwt --version 6.9.0
Add Token Settings:
While generating the JWT access token, few token-specific settings need to be specified.
appsettings.Development.json:
"TokenSettings":{ "Issuer":"localhost:5001", "Audience":"js.app.com", "Key":"SomeRandomlyGeneratedStringSomeRandomlyGeneratedString" }
- The 'Issuer' is like the identification of the server that generated the token. In access token 'iss' claim will be added, it is not user claim, it application claim. The values recommended are URLs for the 'iss' claim, nothing but our JWT API domain.
- The 'Audience' is like the client application which received our token. This builds up a protective layer like token consumption taking place in hands of the appropriate client application. In access token 'aud' claim will be added. So its value can be the domain of the client application. In real-time, this value needs to be fetched from the database or a collection in a configuration file because a single JWT API can serve multiple client applications.
- The 'Key' value is some sort of secret key. For real-time application make sure to use an encrypted string value as your secret key and store it in config.
Shared/TokenSettings.cs:
namespace GraphQL.PureCodeFirst.Auth.Shared { public class TokenSettings { public string Issuer { get; set; } public string Audience { get; set; } public string Key { get; set; } } }Register 'TokenSerttings' type in Startup.cs file
Startup.cs:
services.Configure<TokenSettings>(Configuration.GetSection("TokenSettings"));
Implement Logic To Generate JWT Authentication Token:
In 'InputTypes' folder add a new class like 'LoginInputType.cs'. The 'LoginInputType' is payload object for user authentication.
InputTypes/LoginInputType.cs:
namespace GraphQL.PureCodeFirst.InputTypes { public class LoginInputType { public string Email { get; set; } public string Passowrd { get; set; } } }
Now let's inject 'TokenSettings' into our 'AuthLogic'
Logics/AuthLogic.cs:
Now let's implement a private method like 'ValidatePasswordHash()' to validate the user password for the authentication process.private readonly AuthContext _authContext; private readonly TokenSettings _tokenSettings; public AuthLogic(AuthContext authContext, IOptions<TokenSettings> tokenSettings) { _authContext = authContext; _tokenSettings = tokenSettings.Value; }
Logics/AuthLogic.cs:
private bool ValidatePasswordHash(string password, string dbPassword) { byte[] hashBytes = Convert.FromBase64String(dbPassword); byte[] salt = new byte[16]; Array.Copy(hashBytes, 0, salt, 0, 16); var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 1000); byte[] hash = pbkdf2.GetBytes(20); for (int i = 0; i < 20; i++) { if (hashBytes[i + 16] != hash[i]) { return false; } } return true; }
- In the user registration article(Part-1) we encrypted our passwords like a combination of salt key and password.
- (Line: 3) Getting an array of bytes from our database saved password.
- (Line: 6) Fetching the salt key array of bytes from the 'hashBytes'(database saved password array bytes).
- (Line: 8-9) Encrypting user enter password with salt key, then getting the final encrypted password after the completion of specified iterations. The iteration count specified here must match with the count in the 'PasswordHash()' method(used for user registration).
- Then checking each byte of user-entered password against the database saved password.
Logics/AuthLogic.cs:
private string GetJWTAuthKey(User user, List<UserRoles> roles) { var securtityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenSettings.Key)); var credentials = new SigningCredentials(securtityKey, SecurityAlgorithms.HmacSha256); var claims = new List<Claim>(); claims.Add(new Claim("Email", user.EmailAddress)); claims.Add(new Claim("LastName", user.LastName)); if ((roles?.Count ?? 0) > 0) { foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role.Name)); } } var jwtSecurityToken = new JwtSecurityToken( issuer: _tokenSettings.Issuer, audience: _tokenSettings.Audience, expires: DateTime.Now.AddMinutes(30), signingCredentials: credentials, claims:claims ); return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); }
- (Line: 3)Using our 'Key' generating 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey' instance.
- (Line: 5) Generating 'Microsoft.IdentityModel.Tokens.SigningCredentials' instance using 'SymmetricSecurityKey' and algorithm like 'HmacSha256'.
- (Line: 9-10) Adding 'Email' and 'LastName' as claims
- (Line: 11-17) Adding user roles to claims collection.
- (Line: 19-25) Generating 'JwtSecurityToken' with all necessary configurations to generate the Jwt authentication token.
public string Login(LoginInputType loginInput) { if (string.IsNullOrEmpty(loginInput.Email) || string.IsNullOrEmpty(loginInput.Passowrd)) { return "Invalid Credentials"; } var user = _authContext.User.Where(_ => _.EmailAddress == loginInput.Email).FirstOrDefault(); if (user == null) { return "Invalid Credentials"; } if (!ValidatePasswordHash(loginInput.Passowrd, user.Password)) { return "Invalid Credentials"; } var roles = _authContext.UserRoles.Where(_ => _.UserId == user.UserId).ToList(); return GetJWTAuthKey(user, roles); }
- (Line: 3-7) Validating user-entered payload.
- (Line: 9-13) Checking user registered or not.
- (Line: 5-18) Validating user-entered password against our database password.
- (Line: 20) Fetching user dependant roles.
- (Line: 22) Generating the JWT access token for user authentication.
Create Login Resolver In Mutation:
Finally, we need to create a login resolver method in our mutation type class.
Resolvers/MutationResolver.cs:
public string Login([Service] IAuthLogic authLogic,LoginInputType loginInput) { return authLogic.Login(loginInput); }Now let's frame our Mutation to fetch user Jwt access token.
Mutation:
mutation ($LoginModel:LoginInputTypeInput){ login(loginInput:$LoginModel) }Variable:
{ "LoginModel":{ "email":"test@test.com", "passowrd":"Test@1234" } }So that's all about the generating JWT authentication token in GraphQL. In the next article Part-3, we will implement Jwt token validations and different authorization techniques.
Video Session:
Support Me!
Buy Me A Coffee
PayPal Me
Wrapping Up:
Hopefully, I think this article delivered some useful information on generating JWT user access tokens in the Pure Code First technique in Hot Chocolate GraphQL. I love to have your feedback, suggestions, and better techniques in the comment section below.
Comments
Post a Comment