Skip to content

Commit

Permalink
Merge pull request #17 from evgomes/net-7-update
Browse files Browse the repository at this point in the history
.NET 7 Update and Code Refactoring
  • Loading branch information
evgomes authored Jul 6, 2023
2 parents 2f3fa88 + ea9b001 commit bd58600
Show file tree
Hide file tree
Showing 32 changed files with 194 additions and 198 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JWT API

This example API shows how to implement JSON Web Token authentication and authorization with ASP.NET 6, built from scratch.
This example API shows how to implement JSON Web Token authentication and authorization with ASP.NET Core 7, built from scratch.

### Features
- User registration;
Expand Down
88 changes: 39 additions & 49 deletions src/JWTAPI/JWTAPI/Controllers/LoginController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,43 @@ namespace JWTAPI.Controllers;
[Route("api/")]
public class AuthController : ControllerBase
{
private readonly IMapper _mapper;
private readonly IAuthenticationService _authenticationService;

public AuthController(IMapper mapper, IAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
_mapper = mapper;
}

[HttpPost("login")]
public async Task<IActionResult> LoginAsync(
[FromBody] UserCredentialsResource userCredentials)
{
var response = await _authenticationService
.CreateAccessTokenAsync(userCredentials.Email, userCredentials.Password);

if (!response.Success)
{
return BadRequest(response.Message);
}

var accessTokenResource = _mapper.Map<AccessToken, AccessTokenResource>(response.Token);

return Ok(accessTokenResource);
}

[HttpPost("token/refresh")]
public async Task<IActionResult> RefreshTokenAsync(
[FromBody] RefreshTokenResource refreshTokenResource)
{
var response = await _authenticationService
.RefreshTokenAsync(refreshTokenResource.Token, refreshTokenResource.UserEmail);

if (!response.Success)
{
return BadRequest(response.Message);
}

var tokenResource = _mapper.Map<AccessToken, AccessTokenResource>(response.Token);

return Ok(tokenResource);
}

[HttpPost("token/revoke")]
public IActionResult RevokeToken([FromBody] RevokeTokenResource resource)
{
_authenticationService.RevokeRefreshToken(resource.Token, resource.Email);
return NoContent();
}
private readonly IMapper _mapper;
private readonly IAuthenticationService _authenticationService;

public AuthController(IMapper mapper, IAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
_mapper = mapper;
}

[HttpPost("login")]
public async Task<IActionResult> LoginAsync([FromBody] UserCredentialsResource userCredentials)
{
var response = await _authenticationService.CreateAccessTokenAsync(userCredentials.Email!, userCredentials.Password!);
if (!response.Success)
{
return BadRequest(response.Message);
}

return Ok(_mapper.Map<AccessTokenResource>(response.Token));
}

[HttpPost("token/refresh")]
public async Task<IActionResult> RefreshTokenAsync([FromBody] RefreshTokenResource refreshTokenResource)
{
var response = await _authenticationService.RefreshTokenAsync(refreshTokenResource.Token!, refreshTokenResource.UserEmail!);
if (!response.Success)
{
return BadRequest(response.Message);
}

return Ok(_mapper.Map<AccessTokenResource>(response.Token));
}

[HttpPost("token/revoke")]
public IActionResult RevokeToken([FromBody] RevokeTokenResource resource)
{
_authenticationService.RevokeRefreshToken(resource.Token!, resource.Email!);
return NoContent();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
namespace JWTAPI.Controllers.Resources;
public class RefreshTokenResource

public record RefreshTokenResource
{
[Required]
public string Token { get; set; }
public string? Token { get; init; }

[Required]
[EmailAddress]
[StringLength(255)]
public string UserEmail { get; set; }
public string? UserEmail { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
namespace JWTAPI.Controllers.Resources;

public class RevokeTokenResource
{
[Required]
public string Token { get; set; }
public string? Token { get; init; }

[Required]
public string Email { get; set; }
public string? Email { get; init; }
}
7 changes: 4 additions & 3 deletions src/JWTAPI/JWTAPI/Controllers/Resources/TokenResource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace JWTAPI.Controllers.Resources;

public class AccessTokenResource
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public long Expiration { get; set; }
public string AccessToken { get; init; } = null!;
public string RefreshToken { get; init; } = null!;
public long Expiration { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
namespace JWTAPI.Controllers.Resources;

public class UserCredentialsResource
{
[Required]
[EmailAddress]
[StringLength(255)]
public string Email { get; set; }
public string? Email { get; init; }

[Required]
[StringLength(32)]
public string Password { get; set; }
public string? Password { get; init; }
}
7 changes: 4 additions & 3 deletions src/JWTAPI/JWTAPI/Controllers/Resources/UserResource.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace JWTAPI.Controllers.Resources;
public class UserResource

public record UserResource
{
public int Id { get; set; }
public string Email { get; set; }
public IEnumerable<string> Roles { get; set; }
public string Email { get; set; } = null!;
public IEnumerable<string> Roles { get; set; } = new List<string>();
}
40 changes: 18 additions & 22 deletions src/JWTAPI/JWTAPI/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,26 @@ namespace JWTAPI.Controllers;
[Route("/api/users")]
public class UsersController : ControllerBase
{
private readonly IMapper _mapper;
private readonly IUserService _userService;
private readonly IMapper _mapper;
private readonly IUserService _userService;

public UsersController(IUserService userService, IMapper mapper)
{
_userService = userService;
_mapper = mapper;
}
public UsersController(IUserService userService, IMapper mapper)
{
_userService = userService;
_mapper = mapper;
}

[HttpPost]
public async Task<IActionResult> CreateUserAsync(
[FromBody] UserCredentialsResource userCredentials)
{
var user = _mapper.Map<UserCredentialsResource, User>(userCredentials);
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] UserCredentialsResource userCredentials)
{
var user = _mapper.Map<User>(userCredentials);

var response = await _userService.CreateUserAsync(user, ApplicationRole.Common);
var response = await _userService.CreateUserAsync(user, ApplicationRole.Common);
if (!response.Success)
{
return BadRequest(response.Message);
}

if (!response.Success)
{
return BadRequest(response.Message);
}

var userResource = _mapper.Map<User, UserResource>(response.User);

return Ok(userResource);
}
return Ok(_mapper.Map<UserResource>(response.User));
}
}
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Core/Models/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public class Role

[Required]
[StringLength(50)]
public string Name { get; set; }
public string Name { get; set; } = null!;

public virtual ICollection<UserRole> UsersRole { get; set; } = new Collection<UserRole>();
}
6 changes: 3 additions & 3 deletions src/JWTAPI/JWTAPI/Core/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ public class User
[Required]
[EmailAddress]
[StringLength(255)]
public string Email { get; set; }
public string Email { get; set; } = null!;

[Required]
public string Password { get; set; }
public string Password { get; set; } = null!;

public ICollection<UserRole> UserRoles { get; set; } = new Collection<UserRole>();
public ICollection<UserRole> UserRoles { get; set; } = new Collection<UserRole>();
}
4 changes: 2 additions & 2 deletions src/JWTAPI/JWTAPI/Core/Models/UserRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace JWTAPI.Core.Models;
public class UserRole
{
public int UserId { get; set; }
public User User { get; set; }
public User? User { get; set; }

public int RoleId { get; set; }
public Role Role { get; set; }
public Role? Role { get; set; }
}
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Core/Repositories/IUserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace JWTAPI.Core.Repositories;
public interface IUserRepository
{
Task AddAsync(User user, ApplicationRole[] userRoles);
Task<User> FindByEmailAsync(string email);
Task<User?> FindByEmailAsync(string email);
}
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Core/Security/Tokens/AccessToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ public class AccessToken : JsonWebToken
public AccessToken(string token, long expiration, RefreshToken refreshToken) : base(token, expiration)
{
RefreshToken = refreshToken
?? throw new ArgumentException("Specify a valid refresh token.");
?? throw new ArgumentNullException("Specify a valid refresh token.");
}
}
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Core/Security/Tokens/ITokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ namespace JWTAPI.Core.Security.Tokens;
public interface ITokenHandler
{
AccessToken CreateAccessToken(User user);
RefreshToken TakeRefreshToken(string token, string userEmail);
RefreshToken? TakeRefreshToken(string token, string userEmail);
void RevokeRefreshToken(string token, string userEmail);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace JWTAPI.Core.Security.Tokens;
public class RefreshTokenWithEmail
{
public string Email { get; set; }
public RefreshToken RefreshToken { get; set; }
public string Email { get; set; } = null!;
public RefreshToken RefreshToken { get; set; } = null!;
}
4 changes: 2 additions & 2 deletions src/JWTAPI/JWTAPI/Core/Services/Communication/BaseResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ namespace JWTAPI.Core.Services.Communication;
public abstract class BaseResponse
{
public bool Success { get; protected set; }
public string Message { get; protected set; }
public string? Message { get; protected set; }

public BaseResponse(bool success, string message)
public BaseResponse(bool success, string? message)
{
Success = success;
Message = message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace JWTAPI.Core.Services.Communication;
public class CreateUserResponse : BaseResponse
{
public User User { get; private set; }
public User? User { get; private set; }

public CreateUserResponse(bool success, string message, User user) : base(success, message)
public CreateUserResponse(bool success, string? message, User? user) : base(success, message)
{
User = user;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace JWTAPI.Core.Services.Communication;
public class TokenResponse : BaseResponse
{
public AccessToken Token { get; set; }
public AccessToken? Token { get; set; }

public TokenResponse(bool success, string message, AccessToken token) : base(success, message)
public TokenResponse(bool success, string? message, AccessToken? token) : base(success, message)
{
Token = token;
}
Expand Down
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Core/Services/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ namespace JWTAPI.Core.Services;
public interface IUserService
{
Task<CreateUserResponse> CreateUserAsync(User user, params ApplicationRole[] userRoles);
Task<User> FindByEmailAsync(string email);
Task<User?> FindByEmailAsync(string email);
}
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Extensions/IdentityServiceExtenstions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public static IServiceCollection AddIdentityServices(
{
services.Configure<TokenOptions>(configuration.GetSection("TokenOptions"));

var tokenOptions = configuration.GetSection("TokenOptions").Get<TokenOptions>();
var tokenOptions = configuration.GetSection("TokenOptions").Get<TokenOptions>() ?? throw new ArgumentNullException(nameof(TokenOptions));

var signingConfigurations = new SigningConfigurations(tokenOptions.Secret);

Expand Down
2 changes: 1 addition & 1 deletion src/JWTAPI/JWTAPI/Extensions/MiddlewareExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static IServiceCollection AddCustomSwagger(this IServiceCollection servic
{
Title = "JWT API",
Version = "v4",
Description = "Example API that shows how to implement JSON Web Token authentication and authorization with ASP.NET 6, built from scratch.",
Description = "Example API that shows how to implement JSON Web Token authentication and authorization with ASP.NET Core 7, built from scratch.",
Contact = new OpenApiContact
{
Name = "Evandro Gayer Gomes",
Expand Down
22 changes: 13 additions & 9 deletions src/JWTAPI/JWTAPI/JWTAPI.csproj
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile>bin\Debug\net7.0\JWTAPI.xml</DocumentationFile>
<NoWarn>1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.8" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>

</Project>
Loading

0 comments on commit bd58600

Please sign in to comment.