1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Timeline.Configs;
using Timeline.Entities;
namespace Timeline.Services
{
public class TokenValidationResult
{
public bool IsValid { get; set; }
public UserInfo UserInfo { get; set; }
}
public interface IJwtService
{
/// <summary>
/// Create a JWT token for a given user.
/// Return null if <paramref name="user"/> is null.
/// </summary>
/// <param name="user">The user to generate token.</param>
/// <returns>The generated token or null if <paramref name="user"/> is null.</returns>
string GenerateJwtToken(User user);
/// <summary>
/// Validate a JWT token.
/// Return null is <paramref name="token"/> is null.
/// If token is invalid, return a <see cref="TokenValidationResult"/> with
/// <see cref="TokenValidationResult.IsValid"/> set to false and
/// <see cref="TokenValidationResult.UserInfo"/> set to null.
/// If token is valid, return a <see cref="TokenValidationResult"/> with
/// <see cref="TokenValidationResult.IsValid"/> set to true and
/// <see cref="TokenValidationResult.UserInfo"/> filled with the user info
/// in the token.
/// </summary>
/// <param name="token">The token string to validate.</param>
/// <returns>Null if <paramref name="token"/> is null. Or the result.</returns>
TokenValidationResult ValidateJwtToken(string token);
}
public class JwtService : IJwtService
{
private readonly IOptionsMonitor<JwtConfig> _jwtConfig;
private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
private readonly ILogger<JwtService> _logger;
public JwtService(IOptionsMonitor<JwtConfig> jwtConfig, ILogger<JwtService> logger)
{
_jwtConfig = jwtConfig;
_logger = logger;
}
public string GenerateJwtToken(User user)
{
if (user == null)
return null;
var jwtConfig = _jwtConfig.CurrentValue;
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
identity.AddClaim(new Claim(identity.NameClaimType, user.Username));
identity.AddClaims(user.Roles.Select(role => new Claim(identity.RoleClaimType, role)));
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = identity,
Issuer = jwtConfig.Issuer,
Audience = jwtConfig.Audience,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.SigningKey)), SecurityAlgorithms.HmacSha384),
IssuedAt = DateTime.Now,
Expires = DateTime.Now.AddDays(1)
};
var token = _tokenHandler.CreateToken(tokenDescriptor);
var tokenString = _tokenHandler.WriteToken(token);
return tokenString;
}
public TokenValidationResult ValidateJwtToken(string token)
{
if (token == null)
return null;
var config = _jwtConfig.CurrentValue;
try
{
var principal = _tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidIssuer = config.Issuer,
ValidAudience = config.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SigningKey))
}, out SecurityToken validatedToken);
var identity = principal.Identity as ClaimsIdentity;
var userInfo = new UserInfo
{
Username = identity.FindAll(identity.NameClaimType).Select(claim => claim.Value).Single(),
Roles = identity.FindAll(identity.RoleClaimType).Select(claim => claim.Value).ToArray()
};
return new TokenValidationResult
{
IsValid = true,
UserInfo = userInfo
};
}
catch (Exception e)
{
_logger.LogInformation(e, "Token validation failed! Token is {} .", token);
return new TokenValidationResult { IsValid = false };
}
}
}
}
|