Skip to content

Commit

Permalink
feat(security): Now it uses NetDevPack.Security.Jwt
Browse files Browse the repository at this point in the history
feat(refresh-token): Added RefreshToken support
  • Loading branch information
brunobritodev committed Apr 17, 2022
1 parent 169d221 commit 9427db1
Show file tree
Hide file tree
Showing 27 changed files with 800 additions and 363 deletions.
65 changes: 25 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,59 +73,48 @@ After execute this steps you will be all set to use the Identity in your Applica
### Configuring JWT
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`
```csharp
services.AddJwtConfiguration(Configuration, "AppSettings");
services.AddJwtConfiguration(Configuration).UseNetDevPackIdentity();;
```

>**Note:** If you don't inform the configuration name the value adopted will be _AppJwtSettings_

Set your `appsettings.json` file with this values:

```json
"AppSettings": {
"SecretKey": "MYSECRETSUPERSECRET",
"Expiration": 2,
"Issuer": "SampleApp",
"Audience": "https://localhost"
"AppJwtSettings": {
"Expiration": 1,
"Issuer": "https://my-application.com",
"Audience": "MyApplication.Name"
}
```

|Key|Meaning|
|--|--|
|SecretKey | Is your key to build JWT. This value need to be stored in a safe place in the production way |
|Expiration| Expiration time in hours |
|Issuer| The name of the JWT issuer |
|Audience| The domain that the JWT will be valid. Can be a string collection |
|Key|Meaning|Default
|--|--|---|
|Expiration| Expiration time (in hours) | 1 |
|Issuer| The name of the JWT issuer | NetDevPack.Identity |
|Audience| The domain that the JWT will be valid | Api |
|RefreshTokenExpiration | Refresh token expiration (In Days) | 30 |
|SecretKey `Deprecated` | Is your key to build JWT. **Read notes**| Do not use it |

>**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.
### Generating JWT
You will need to set some dependencies in your Authentication Controller:
You will need to set a single dependency in your Authentication Controller:

```csharp
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly AppJwtSettings _appJwtSettings;

public AuthController(SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
IOptions<AppJwtSettings> appJwtSettings)
public AuthController(IJwtBuilder jwtBuilder)
{
_signInManager = signInManager;
_userManager = userManager;
_appJwtSettings = appJwtSettings.Value;
_jwtBuilder = jwtBuilder;
}
```

>**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.
>
>**Note:** The _SignInManager_ and _UserManager_ classes is native from Identity and provided in NetDevPack.Identity. You just need to inject it in your controller.
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:

```csharp
return new JwtBuilder()
.WithUserManager(_userManager)
.WithJwtSettings(_appJwtSettings)
return _jwtBuilder
.WithEmail(email)
.WithRefreshToken()
.BuildToken();
```

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

```csharp
return new JwtBuilder()
.WithUserManager(_userManager)
.WithJwtSettings(_appJwtSettings)
return _jwtBuilder
.WithEmail(email)
.WithJwtClaims()
.WithUserClaims()
.WithUserRoles()
.WithRefreshToken()
.BuildToken();
```

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

```csharp
return new JwtBuilder()
.WithUserManager(_userManager)
.WithJwtSettings(_appJwtSettings)
return _jwtBuilder
.WithEmail(email)
.WithJwtClaims()
.WithUserClaims()
.WithUserRoles()
.BuildUserResponse() as UserResponse;
.WithRefreshToken()
.BuildUserResponse();
```

>**Note:** The safe cast to `UserResponse` is needed because is a subtype of `UserResponse<TKey>`.
## Examples
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.

## Compatibility
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.
The **NetDevPack.Identity** was developed to be implemented in **ASP.NET Core**. It support all .NET versions since 3.1.

## About
.NET DevPack.Identity was developed by [Eduardo Pires](http://eduardopires.net.br) under the [MIT license](LICENSE).
Expand Down
90 changes: 0 additions & 90 deletions src/NetDevPack.Identity/Abstractions.cs

This file was deleted.

19 changes: 19 additions & 0 deletions src/NetDevPack.Identity/Interfaces/IJwtBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Threading.Tasks;
using NetDevPack.Identity.Jwt;
using NetDevPack.Identity.Jwt.Model;

namespace NetDevPack.Identity.Interfaces;

public interface IJwtBuilder
{
IJwtBuilder WithEmail(string email);
IJwtBuilder WithUsername(string username);
IJwtBuilder WithUserId(string id);
IJwtBuilder WithJwtClaims();
IJwtBuilder WithUserClaims();
IJwtBuilder WithUserRoles();
IJwtBuilder WithRefreshToken();
Task<string> BuildToken();
Task<UserResponse> BuildUserResponse();
Task<RefreshTokenValidation> ValidateRefreshToken(string refreshToken);
}
63 changes: 44 additions & 19 deletions src/NetDevPack.Identity/Jwt/Abstractions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,67 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using NetDevPack.Security.Jwt.AspNetCore;
using NetDevPack.Security.Jwt.Core;

namespace NetDevPack.Identity.Jwt
{
public static class Abstractions
{
public static IServiceCollection AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration, string appJwtSettingsKey = null)
public static IServiceCollection AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration, string appJwtSettingsKey = "AppJwtSettings")
{
if (services == null) throw new ArgumentException(nameof(services));
if (configuration == null) throw new ArgumentException(nameof(configuration));

var appSettingsSection = configuration.GetSection(appJwtSettingsKey ?? "AppJwtSettings");
var appSettingsSection = configuration.GetSection(appJwtSettingsKey);
services.Configure<AppJwtSettings>(appSettingsSection);

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

services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
if (!string.IsNullOrEmpty(appSettings.SecretKey))
SymetricKeyConfiguration(services, appSettings);
else
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = appSettings.Audience,
ValidIssuer = appSettings.Issuer
};
});
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = appSettings.Audience,
ValidIssuer = appSettings.Issuer,
};
});
services.AddDataProtection();
services.AddJwksManager().UseJwtValidation();
}

return services;
}

private static void SymetricKeyConfiguration(IServiceCollection services, AppJwtSettings appSettings)
{
var key = Encoding.ASCII.GetBytes(appSettings.SecretKey);

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = appSettings.Audience,
ValidIssuer = appSettings.Issuer
};
});
}
}
}
31 changes: 25 additions & 6 deletions src/NetDevPack.Identity/Jwt/AppJwtSettings.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

namespace NetDevPack.Identity.Jwt
{
public class AppJwtSettings
{
[Obsolete("For better security use IJwtBuilder and set null for this field")]
public string SecretKey { get; set; }
public int Expiration { get; set; }
public string Issuer { get; set; }
public IList<string> Issuers { get; set; }
public string Audience { get; set; }
public IList<string> Audiences { get; set; }

/// <summary>
/// Hours
/// </summary>
public int Expiration { get; set; } = 1;
/// <summary>
/// Days
/// </summary>
public int RefreshTokenExpiration { get; set; } = 30;
public string Issuer { get; set; } = "NetDevPack.Identity";
public string Audience { get; set; } = "Api";

/// <summary>
/// One Time => Only the lastest refresh token is valid. Ignore olders refresh token.
/// Better security and best suit for application with only one Frontend
///
/// ReUse => Accept olders refresh tokens
/// Decrease security but better for scenarios where there are more than one frontend. Like a browser + mobile app
/// </summary>
public RefreshTokenType RefreshTokenType { get; set; } = RefreshTokenType.OneTime;


}
}
24 changes: 24 additions & 0 deletions src/NetDevPack.Identity/Jwt/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;

namespace NetDevPack.Identity.Jwt
{
internal static class Extensions
{
public static void RemoveRefreshToken(this ICollection<Claim> claims)
{
var refreshToken = claims.FirstOrDefault(f => f.Type == "LastRefreshToken");
if (refreshToken is not null)
claims.Remove(refreshToken);
}

public static string GetJwtId(this ClaimsIdentity principal)
{
return principal.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
}
}
}
Loading

0 comments on commit 9427db1

Please sign in to comment.