Skip to content

Commit e360d50

Browse files
Merge pull request #18 from NetDevPack/feat/refresh-token
Refresh Token + Security.Jwt integration
2 parents ee2fbcf + 1b98228 commit e360d50

28 files changed

Lines changed: 802 additions & 370 deletions

.github/hooks/commit-msg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ if ! head -1 "$1" | grep -qE "^(feat|fix|ci|chore|docs|test|style|refactor|chk)(
3131
echo "For more information check https://www.conventionalcommits.org/en/v1.0.0/ for more details" >&2
3232
exit 1
3333
fi
34-
if ! head -1 "$1" | grep -qE "^.{1,50}$"; then
34+
if ! head -1 "$1" | grep -qE "^.{1,150}$"; then
3535
echo "Aborting commit. Your commit message is too long." >&2
3636
exit 1
3737
fi

README.md

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -73,59 +73,46 @@ After execute this steps you will be all set to use the Identity in your Applica
7373
### Configuring JWT
7474
If you want to generate JSON Web Tokens in your application you need to add the JWT configuration in `ConfigureServices` method of your `startup.cs`
7575
```csharp
76-
services.AddJwtConfiguration(Configuration, "AppSettings");
76+
services.AddJwtConfiguration(Configuration).UseNetDevPackIdentity();;
7777
```
7878

79-
>**Note:** If you don't inform the configuration name the value adopted will be _AppJwtSettings_
80-
81-
8279
Set your `appsettings.json` file with this values:
8380

8481
```json
85-
"AppSettings": {
86-
"SecretKey": "MYSECRETSUPERSECRET",
87-
"Expiration": 2,
88-
"Issuer": "SampleApp",
89-
"Audience": "https://localhost"
82+
"AppJwtSettings": {
83+
"Issuer": "https://my-application.com",
84+
"Audience": "MyApplication.Name"
9085
}
9186
```
87+
It's possible to configure some aspects of token
9288

93-
|Key|Meaning|
94-
|--|--|
95-
|SecretKey | Is your key to build JWT. This value need to be stored in a safe place in the production way |
96-
|Expiration| Expiration time in hours |
97-
|Issuer| The name of the JWT issuer |
98-
|Audience| The domain that the JWT will be valid. Can be a string collection |
89+
|Key|Meaning|Default
90+
|--|--|---|
91+
|Expiration| Expiration time (in hours) | 1 |
92+
|Issuer| The name of the JWT issuer | NetDevPack.Identity |
93+
|Audience| The domain that the JWT will be valid | Api |
94+
|RefreshTokenExpiration | Refresh token expiration (In Days) | 30 |
95+
|SecretKey `Deprecated` | Is your key to build JWT. **Read notes**| Do not use it |
96+
97+
>**Note:** Now we are using [NetDevPack.Security.Jwt](https://github.com/NetDevPack/Security.Jwt) to generate and Store your keys. It generate a RSA 2048 by default. You can check the project for more info.
9998
10099
### Generating JWT
101-
You will need to set some dependencies in your Authentication Controller:
100+
You will need to set a single dependency in your Authentication Controller:
102101

103102
```csharp
104-
private readonly SignInManager<IdentityUser> _signInManager;
105-
private readonly UserManager<IdentityUser> _userManager;
106-
private readonly AppJwtSettings _appJwtSettings;
107103

108-
public AuthController(SignInManager<IdentityUser> signInManager,
109-
UserManager<IdentityUser> userManager,
110-
IOptions<AppJwtSettings> appJwtSettings)
104+
public AuthController(IJwtBuilder jwtBuilder)
111105
{
112-
_signInManager = signInManager;
113-
_userManager = userManager;
114-
_appJwtSettings = appJwtSettings.Value;
106+
_jwtBuilder = jwtBuilder;
115107
}
116108
```
117109

118-
>**Note:** The _AppJwtSettings_ is our dependency and is configured internally during JWT setup (in `startup.cs` file). You just need to inject it in your controller.
119-
>
120-
>**Note:** The _SignInManager_ and _UserManager_ classes is native from Identity and provided in NetDevPack.Identity. You just need to inject it in your controller.
121-
122110
After user register or login process you can generate a JWT to respond the request. Use our implementation, you just need inform the user email and the dependencies injected in your controller:
123111

124112
```csharp
125-
return new JwtBuilder()
126-
.WithUserManager(_userManager)
127-
.WithJwtSettings(_appJwtSettings)
113+
return _jwtBuilder
128114
.WithEmail(email)
115+
.WithRefreshToken()
129116
.BuildToken();
130117
```
131118

@@ -135,13 +122,12 @@ return new JwtBuilder()
135122
You can call more methods in `JwtBuilder` to provide more information about the user:
136123

137124
```csharp
138-
return new JwtBuilder()
139-
.WithUserManager(_userManager)
140-
.WithJwtSettings(_appJwtSettings)
125+
return _jwtBuilder
141126
.WithEmail(email)
142127
.WithJwtClaims()
143128
.WithUserClaims()
144129
.WithUserRoles()
130+
.WithRefreshToken()
145131
.BuildToken();
146132
```
147133

@@ -155,23 +141,20 @@ return new JwtBuilder()
155141
If you want return your complex object `UserResponse` you need to change the last method to:
156142

157143
```csharp
158-
return new JwtBuilder()
159-
.WithUserManager(_userManager)
160-
.WithJwtSettings(_appJwtSettings)
144+
return _jwtBuilder
161145
.WithEmail(email)
162146
.WithJwtClaims()
163147
.WithUserClaims()
164148
.WithUserRoles()
165-
.BuildUserResponse() as UserResponse;
149+
.WithRefreshToken()
150+
.BuildUserResponse();
166151
```
167152

168-
>**Note:** The safe cast to `UserResponse` is needed because is a subtype of `UserResponse<TKey>`.
169-
170153
## Examples
171154
Use the [sample application](https://github.com/NetDevPack/NetDevPack.Identity/tree/master/src/Samples/AspNetCore.Jwt.Sample) to understand how NetDevPack.Identity can be implemented and help you to decrease the complexity of your application and development time.
172155

173156
## Compatibility
174-
The **NetDevPack.Identity** was developed to be implemented in **ASP.NET Core 3.1** `LTS` applications, in the next versions will be add the 2.1 `LTS` support.
157+
The **NetDevPack.Identity** was developed to be implemented in **ASP.NET Core**. It support all .NET versions since 3.1.
175158

176159
## About
177160
.NET DevPack.Identity was developed by [Eduardo Pires](http://eduardopires.net.br) under the [MIT license](LICENSE).

src/NetDevPack.Identity/Abstractions.cs

Lines changed: 0 additions & 90 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Threading.Tasks;
2+
using NetDevPack.Identity.Jwt;
3+
using NetDevPack.Identity.Jwt.Model;
4+
5+
namespace NetDevPack.Identity.Interfaces;
6+
7+
public interface IJwtBuilder
8+
{
9+
IJwtBuilder WithEmail(string email);
10+
IJwtBuilder WithUsername(string username);
11+
IJwtBuilder WithUserId(string id);
12+
IJwtBuilder WithJwtClaims();
13+
IJwtBuilder WithUserClaims();
14+
IJwtBuilder WithUserRoles();
15+
IJwtBuilder WithRefreshToken();
16+
Task<string> BuildToken();
17+
Task<UserResponse> BuildUserResponse();
18+
Task<RefreshTokenValidation> ValidateRefreshToken(string refreshToken);
19+
}

src/NetDevPack.Identity/Jwt/Abstractions.cs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,67 @@
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.DependencyInjection;
66
using Microsoft.IdentityModel.Tokens;
7+
using NetDevPack.Security.Jwt.AspNetCore;
8+
using NetDevPack.Security.Jwt.Core;
79

810
namespace NetDevPack.Identity.Jwt
911
{
1012
public static class Abstractions
1113
{
12-
public static IServiceCollection AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration, string appJwtSettingsKey = null)
14+
public static IServiceCollection AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration, string appJwtSettingsKey = "AppJwtSettings")
1315
{
1416
if (services == null) throw new ArgumentException(nameof(services));
1517
if (configuration == null) throw new ArgumentException(nameof(configuration));
1618

17-
var appSettingsSection = configuration.GetSection(appJwtSettingsKey ?? "AppJwtSettings");
19+
var appSettingsSection = configuration.GetSection(appJwtSettingsKey);
1820
services.Configure<AppJwtSettings>(appSettingsSection);
1921

2022
var appSettings = appSettingsSection.Get<AppJwtSettings>();
21-
var key = Encoding.ASCII.GetBytes(appSettings.SecretKey);
2223

23-
services.AddAuthentication(x =>
24-
{
25-
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
26-
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
27-
}).AddJwtBearer(x =>
24+
if (!string.IsNullOrEmpty(appSettings.SecretKey))
25+
SymetricKeyConfiguration(services, appSettings);
26+
else
2827
{
29-
x.RequireHttpsMetadata = true;
30-
x.SaveToken = true;
31-
x.TokenValidationParameters = new TokenValidationParameters
28+
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
3229
{
33-
ValidateIssuerSigningKey = true,
34-
IssuerSigningKey = new SymmetricSecurityKey(key),
35-
ValidateIssuer = true,
36-
ValidateAudience = true,
37-
ValidAudience = appSettings.Audience,
38-
ValidIssuer = appSettings.Issuer
39-
};
40-
});
30+
options.RequireHttpsMetadata = true;
31+
options.SaveToken = true;
32+
options.TokenValidationParameters = new TokenValidationParameters
33+
{
34+
ValidateIssuer = true,
35+
ValidateAudience = true,
36+
ValidateLifetime = true,
37+
ValidateIssuerSigningKey = true,
38+
ValidAudience = appSettings.Audience,
39+
ValidIssuer = appSettings.Issuer,
40+
};
41+
});
42+
services.AddDataProtection();
43+
services.AddJwksManager().UseJwtValidation();
44+
}
4145

4246
return services;
4347
}
48+
49+
private static void SymetricKeyConfiguration(IServiceCollection services, AppJwtSettings appSettings)
50+
{
51+
var key = Encoding.ASCII.GetBytes(appSettings.SecretKey);
52+
53+
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
54+
.AddJwtBearer(options =>
55+
{
56+
options.RequireHttpsMetadata = true;
57+
options.SaveToken = true;
58+
options.TokenValidationParameters = new TokenValidationParameters
59+
{
60+
ValidateIssuerSigningKey = true,
61+
IssuerSigningKey = new SymmetricSecurityKey(key),
62+
ValidateIssuer = true,
63+
ValidateAudience = true,
64+
ValidAudience = appSettings.Audience,
65+
ValidIssuer = appSettings.Issuer
66+
};
67+
});
68+
}
4469
}
4570
}
Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23

34
namespace NetDevPack.Identity.Jwt
45
{
56
public class AppJwtSettings
67
{
8+
[Obsolete("For better security use IJwtBuilder and set null for this field")]
79
public string SecretKey { get; set; }
8-
public int Expiration { get; set; }
9-
public string Issuer { get; set; }
10-
public IList<string> Issuers { get; set; }
11-
public string Audience { get; set; }
12-
public IList<string> Audiences { get; set; }
10+
11+
/// <summary>
12+
/// Hours
13+
/// </summary>
14+
public int Expiration { get; set; } = 1;
15+
/// <summary>
16+
/// Days
17+
/// </summary>
18+
public int RefreshTokenExpiration { get; set; } = 30;
19+
public string Issuer { get; set; } = "NetDevPack.Identity";
20+
public string Audience { get; set; } = "Api";
21+
22+
/// <summary>
23+
/// One Time => Only the lastest refresh token is valid. Ignore olders refresh token.
24+
/// Better security and best suit for application with only one Frontend
25+
///
26+
/// ReUse => Accept olders refresh tokens
27+
/// Decrease security but better for scenarios where there are more than one frontend. Like a browser + mobile app
28+
/// </summary>
29+
public RefreshTokenType RefreshTokenType { get; set; } = RefreshTokenType.OneTime;
30+
31+
1332
}
1433
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Security.Claims;
5+
using System.Text;
6+
using Microsoft.IdentityModel.JsonWebTokens;
7+
8+
namespace NetDevPack.Identity.Jwt
9+
{
10+
internal static class Extensions
11+
{
12+
public static void RemoveRefreshToken(this ICollection<Claim> claims)
13+
{
14+
var refreshToken = claims.FirstOrDefault(f => f.Type == "LastRefreshToken");
15+
if (refreshToken is not null)
16+
claims.Remove(refreshToken);
17+
}
18+
19+
public static string GetJwtId(this ClaimsIdentity principal)
20+
{
21+
return principal.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)