| | | 1 | | using System.IdentityModel.Tokens.Jwt; |
| | | 2 | | using System.Security.Claims; |
| | | 3 | | using System.Text; |
| | | 4 | | using Microsoft.IdentityModel.Tokens; |
| | | 5 | | |
| | | 6 | | namespace ArturRios.Jwt; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Creates and validates JSON Web Tokens (JWTs) based on a <see cref="JwtConfiguration"/>. |
| | | 10 | | /// </summary> |
| | | 11 | | public class JwtHandler |
| | | 12 | | { |
| | 6 | 13 | | private readonly JwtSecurityTokenHandler _handler = new(); |
| | | 14 | | |
| | | 15 | | /// <summary> |
| | | 16 | | /// Creates a signed JWT (using HMAC-SHA256) from the given configuration. |
| | | 17 | | /// </summary> |
| | | 18 | | /// <param name="configuration">The issuer, audience, expiration, signing secret and claims to embed in the token.</ |
| | | 19 | | /// <returns>The serialized, signed JWT.</returns> |
| | | 20 | | public string CreateToken(JwtConfiguration configuration) |
| | 5 | 21 | | { |
| | 5 | 22 | | var key = string.IsNullOrWhiteSpace(configuration.Secret) ? [] : Encoding.ASCII.GetBytes(configuration.Secret); |
| | | 23 | | |
| | 10 | 24 | | var claimsList = configuration.Claims.Select(c => new Claim(c.Key, c.Value)).ToList(); |
| | | 25 | | |
| | 5 | 26 | | ClaimsIdentity identity = new(claimsList); |
| | | 27 | | |
| | 5 | 28 | | var creationDate = DateTime.Now; |
| | 5 | 29 | | var expirationDate = creationDate + TimeSpan.FromSeconds(configuration.ExpirationInSeconds); |
| | | 30 | | |
| | 5 | 31 | | JwtSecurityTokenHandler handler = new(); |
| | | 32 | | |
| | 5 | 33 | | var token = handler.CreateToken(new SecurityTokenDescriptor |
| | 5 | 34 | | { |
| | 5 | 35 | | Issuer = configuration.Issuer, |
| | 5 | 36 | | Audience = configuration.Audience, |
| | 5 | 37 | | SigningCredentials = |
| | 5 | 38 | | new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature), |
| | 5 | 39 | | Subject = identity, |
| | 5 | 40 | | NotBefore = creationDate, |
| | 5 | 41 | | Expires = expirationDate |
| | 5 | 42 | | }); |
| | | 43 | | |
| | 5 | 44 | | return handler.WriteToken(token); |
| | 5 | 45 | | } |
| | | 46 | | |
| | | 47 | | /// <summary> |
| | | 48 | | /// Reads a JWT and extracts the user id from its "id" claim, without validating the token's signature. |
| | | 49 | | /// </summary> |
| | | 50 | | /// <param name="token">The JWT to read.</param> |
| | | 51 | | /// <returns>The user id from the token's "id" claim, or <see langword="null"/> if the token cannot be read.</return |
| | | 52 | | public int? GetUserIdFromToken(string token) |
| | 2 | 53 | | { |
| | 2 | 54 | | if (ReadToken(token) is not JwtSecurityToken jwtToken) |
| | 1 | 55 | | { |
| | 1 | 56 | | return null; |
| | | 57 | | } |
| | | 58 | | |
| | 2 | 59 | | return int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); |
| | 2 | 60 | | } |
| | | 61 | | |
| | | 62 | | /// <summary> |
| | | 63 | | /// Validates a JWT's signature against the given secret. |
| | | 64 | | /// </summary> |
| | | 65 | | /// <param name="token">The JWT to validate.</param> |
| | | 66 | | /// <param name="secret">The secret key expected to have been used to sign the token.</param> |
| | | 67 | | /// <returns><see langword="true"/> if the token's signature is valid; otherwise, <see langword="false"/>.</returns> |
| | | 68 | | public async Task<bool> IsTokenValidAsync(string token, string secret) |
| | 3 | 69 | | { |
| | 3 | 70 | | var key = string.IsNullOrWhiteSpace(secret) ? [] : Encoding.ASCII.GetBytes(secret); |
| | | 71 | | |
| | 3 | 72 | | var output = await _handler.ValidateTokenAsync(token, |
| | 3 | 73 | | new TokenValidationParameters |
| | 3 | 74 | | { |
| | 3 | 75 | | ValidateIssuerSigningKey = true, |
| | 3 | 76 | | IssuerSigningKey = new SymmetricSecurityKey(key), |
| | 3 | 77 | | ValidateIssuer = false, |
| | 3 | 78 | | ValidateAudience = false, |
| | 3 | 79 | | ClockSkew = TimeSpan.Zero |
| | 3 | 80 | | }); |
| | | 81 | | |
| | 3 | 82 | | return output.IsValid; |
| | 3 | 83 | | } |
| | | 84 | | |
| | | 85 | | /// <summary> |
| | | 86 | | /// Reads a JWT without validating its signature, returning <see langword="null"/> if it cannot be read. |
| | | 87 | | /// </summary> |
| | | 88 | | private SecurityToken? ReadToken(string token) |
| | 2 | 89 | | { |
| | 2 | 90 | | return _handler.CanReadToken(token) ? _handler.ReadToken(token) : null; |
| | 2 | 91 | | } |
| | | 92 | | } |