Skip to content

Commit

Permalink
feat : Created IAuthenticateService and removed the controller's resp…
Browse files Browse the repository at this point in the history
…onsibility for generating jwt
  • Loading branch information
RondineleG committed Sep 17, 2023
1 parent 00b8b0d commit a2bad2f
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.3" />
</ItemGroup>

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

</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Browl.Service.AuthSecurity.API.Configuration;
using Browl.Service.AuthSecurity.API.Service;

namespace Browl.Service.AuthSecurity.API.Configuration;

public static class ApiConfig
{
Expand All @@ -7,6 +9,8 @@ public static IServiceCollection AddApiConfiguration(this IServiceCollection ser
_ = services.AddControllers();
_ = services.AddEndpointsApiExplorer();

services.AddScoped<IAuthenticateService, AuthenticateService>();

var builder = new ConfigurationBuilder()
.SetBasePath(hostEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,35 @@

using Browl.Service.AuthSecurity.API.Controllers.Base;
using Browl.Service.AuthSecurity.API.Entities;
using Browl.Service.AuthSecurity.API.Service;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace Browl.Service.AuthSecurity.API.Controllers;

/// <summary>
/// AuthController handles authentication operations like user registration and login.
/// </summary>
/// <remarks>
/// This API controller inherits from MainController to get common error handling methods.
/// It depends on ASP.NET Core Identity for user management.
///
/// The main methods are:
///
/// - Register: Creates a new user account based on a User model.
/// - Login: Authenticates a user based on a UserLogin model.
/// - GenerateJWT: Generates a JWT token for an authenticated user.
/// - GetClaimsUser: Gets identity claims for a user.
/// - EncodeToken: Encodes a JWT token from identity claims.
///
/// Both Register and Login return a custom API response via the CustomResponse method.
/// The JWT token is added to the response on success.
/// </remarks>

[Route("api/identity/auth")]
public class AuthController : MainController
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly AppSettings _appSettings;

private readonly IAuthenticateService _authenticateService;
public AuthController(SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
IOptions<AppSettings> appSettings)
IOptions<AppSettings> appSettings,
IAuthenticateService authenticateService)
{
_signInManager = signInManager;
_userManager = userManager;
_appSettings = appSettings.Value;
_authenticateService = authenticateService;
}

/// <summary>
/// Registers a new user.
/// </summary>
/// <param name="userRegister">The user object containing registration details.</param>
/// <returns>The result of the registration.</returns>

[HttpPost("new-account")]
public async Task<ActionResult> Register(UserRegister userRegister)
{
Expand All @@ -77,7 +58,7 @@ public async Task<ActionResult> Register(UserRegister userRegister)

if (result.Succeeded)
{
return CustomResponse(await GenerateJWT(userRegister.Email));
return CustomResponse(await _authenticateService.GenerateJWT(userRegister.Email));
}

foreach (var error in result.Errors)
Expand All @@ -88,18 +69,7 @@ public async Task<ActionResult> Register(UserRegister userRegister)
return CustomResponse();
}

/// <summary>
/// Authenticates a user.
/// </summary>
/// <param name="usuarioLogin">The user login object containing login details.</param>
/// <returns>The result of the authentication.</returns>

/// <summary>
/// Authenticates a user.
/// </summary>
/// <param name="usuarioLogin">The user login object containing login details.</param>
/// <returns>The result of the authentication.</returns>


[HttpPost("authenticate")]
public async Task<ActionResult> Login(UserLogin usuarioLogin)
{
Expand All @@ -113,7 +83,7 @@ public async Task<ActionResult> Login(UserLogin usuarioLogin)

if (result.Succeeded)
{
return CustomResponse(await GenerateJWT(usuarioLogin.Email));
return CustomResponse(await _authenticateService.GenerateJWT(usuarioLogin.Email));
}

if (result.IsLockedOut)
Expand All @@ -125,134 +95,5 @@ public async Task<ActionResult> Login(UserLogin usuarioLogin)
AddErrorProcessing("Incorrect username or password");
return CustomResponse();
}

/// <summary>
/// Generates a JWT token for a user.
/// </summary>
/// <param name="email">The email of the user.</param>
/// <returns>The generated JWT token.</returns>

private async Task<UserResponse> GenerateJWT(string email)
{
var user = await _userManager.FindByEmailAsync(email).ConfigureAwait(false);
var claims = await _userManager.GetClaimsAsync(user).ConfigureAwait(false);
var identityClaims = await GetClaimsUser(claims, user).ConfigureAwait(false);
var encodedToken = EncodeToken(identityClaims);
return GetResponseToken(encodedToken, user, claims);
}

/// <summary>
/// Retrieves claims for a user.
/// </summary>
/// <param name="claims">The existing claims for the user.</param>
/// <param name="user">The user object.</param>
/// <returns>The claims identity for the user.</returns>
private async Task<ClaimsIdentity> GetClaimsUser(ICollection<Claim> claims, IdentityUser user)
{
var userRoles = await _userManager.GetRolesAsync(user);
claims.Add(new Claim(JwtRegisteredClaimNames.Sub, user.Id));
claims.Add(new Claim(JwtRegisteredClaimNames.Email, user.Email));
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, ToUnixEpochDate(DateTime.UtcNow).ToString()));
claims.Add(new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(DateTime.UtcNow).ToString(), ClaimValueTypes.Integer64));
foreach (var userRole in userRoles)
{
claims.Add(new Claim("role", userRole));
}
ClaimsIdentity identityClaims = new();
identityClaims.AddClaims(claims);

return identityClaims;
}

/// <summary>
/// Encodes a JWT token from a ClaimsIdentity object.
/// </summary>
/// <param name="identityClaims">The ClaimsIdentity containing the claims for the token.</param>
/// <returns>The encoded JWT token string.</returns>
/// <remarks>
/// This method:
/// - Creates a JwtSecurityTokenHandler instance
/// - Gets the secret key from the app settings
/// - Creates a SecurityTokenDescriptor with:
/// - Issuer, Audience, Subject, Expiration from app settings
/// - SigningCredentials using HMAC SHA256 algorithm
/// - Writes the token using the token handler
/// - Returns the encoded token string
/// </remarks>

private string EncodeToken(ClaimsIdentity identityClaims)
{
JwtSecurityTokenHandler tokenHandler = new();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Issuer = _appSettings.Issuer,
Audience = _appSettings.ValidOn,
Subject = identityClaims,
Expires = DateTime.UtcNow.AddHours(_appSettings.ExpirationHours),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
});

return tokenHandler.WriteToken(token);
}


/// <summary>
/// Returns a UserResponse object containing the encoded JWT token and user details.
/// </summary>
/// <param name="encodedToken">The encoded JWT token string.</param>
/// <param name="user">The IdentityUser object for the authenticated user.</param>
/// <param name="claims">The list of claims for the user.</param>
/// <returns>A UserResponse object containing the encoded token and user details.</returns>
/// <remarks>
/// This method creates and populates a UserResponse object with:
/// - The encoded JWT token string
/// - The token expiration time based on app settings
/// - A UserToken object containing:
/// - The user ID
/// - Email
/// - The user claims
///
/// The UserResponse is returned to the client with the authentication response.
/// </remarks>
private UserResponse GetResponseToken(string encodedToken, IdentityUser user, IEnumerable<Claim> claims)
{
var expiresIn = TimeSpan.FromHours(_appSettings.ExpirationHours).TotalSeconds;
var userClaims = claims.Select(c => new UserClaim { Type = c.Type, Value = c.Value }).ToList();

var response = new UserResponse
{
AccessToken = encodedToken,
ExpiresIn = expiresIn,
UserToken = new UserToken
{
Id = user.Id,
Email = user.Email,
UserClaims = userClaims
}
};

return response;
}

/// <summary>
/// Converts a DateTime to a Unix epoch timestamp in seconds.
/// </summary>
/// <param name="date">The DateTime to convert.</param>
/// <returns>The Unix timestamp in seconds.</returns>
/// <remarks>
/// The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970.
///
/// This method converts the provided DateTime to a long representing seconds since the Unix epoch by:
///
/// - Calling ToUniversalTime() to convert to UTC.
/// - Subtracting the Unix epoch DateTimeOffset (January 1, 1970).
/// - Calculating total seconds by calling TotalSeconds on the timespan.
/// - Casting the double to a long.
/// - Rounding the seconds value.
///
/// The returned long can be used as a Unix timestamp.
/// </remarks>
private static long ToUnixEpochDate(DateTime date) => DateTimeOffset.UtcNow.ToUnixTimeSeconds();

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public static async Task SeedSuperAdminAsync(UserManager<User> userManager, Role
LastName = "Guimaraes",
UserNameChangeLimit = 10,
EmailConfirmed = true,
PhoneNumberConfirmed = true
PhoneNumberConfirmed = true,

};
if (userManager.Users.All(u => u.Id != defaultUser.Id))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

public class AppSettings
{
public required string Secret { get; set; }
public string Secret { get; set; }
public int ExpirationHours { get; set; }
public required string Issuer { get; set; }
public required string ValidOn { get; set; }
public string Issuer { get; set; }
public string ValidOn { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
namespace Browl.Service.AuthSecurity.API.Entities;
public class User : IdentityUser
{
public required string UserName { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int UserNameChangeLimit { get; set; } = 5;
public required byte[] ProfilePicture { get; set; }
public required string Email { get; set; }
public required string Password { get; set; }
public required string PasswordConfirmation { get; set; }
public byte[] ProfilePicture { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string PasswordConfirmation { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ public class UserLogin
{
[Required(ErrorMessage = "Field {0} is mandatory")]
[EmailAddress(ErrorMessage = "Field {0} is in an invalid format")]
public required string Email { get; set; }
public string Email { get; set; }

[Required(ErrorMessage = "Field {0} is mandatory")]
[StringLength(100, ErrorMessage = "Field {0} must be between {2} and {1} characters", MinimumLength = 6)]
public required string Password { get; set; }
public string Password { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,37 @@ namespace Browl.Service.AuthSecurity.API.Entities;

public class UserRegister
{
public UserRegister(string userName, string firstName, string lastName, int userNameChangeLimit, byte[] profilePicture, string email, string password, string passwordConfirmation)
{
UserName = userName;
FirstName = firstName;
LastName = lastName;
UserNameChangeLimit = userNameChangeLimit;
ProfilePicture = profilePicture;
Email = email;
Password = password;
PasswordConfirmation = passwordConfirmation;
}

[Required(ErrorMessage = "Field {0} is mandatory")]
public required string UserName { get; set; }
public string UserName { get; set; }

[Required(ErrorMessage = "Field {0} is mandatory")]
public required string FirstName { get; set; }
public string FirstName { get; set; }
[Required(ErrorMessage = "Field {0} is mandatory")]
public required string LastName { get; set; }
public string LastName { get; set; }
public int UserNameChangeLimit { get; set; } = 5;
[Required(ErrorMessage = "Field {0} is mandatory")]
public required byte[] ProfilePicture { get; set; }
public byte[] ProfilePicture { get; set; }

[Required(ErrorMessage = "Field {0} is mandatory")]
[EmailAddress(ErrorMessage = "Field {0} is in an invalid format")]
public required string Email { get; set; }
public string Email { get; set; }

[Required(ErrorMessage = "Field {0} is mandatory")]
[StringLength(100, ErrorMessage = "Field {0} must be between {2} and {1} characters", MinimumLength = 6)]
public required string Password { get; set; }
public string Password { get; set; }

[Compare("Password", ErrorMessage = "Passwords don't match.")]
public required string PasswordConfirmation { get; set; }
public string PasswordConfirmation { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ public class UserResponse
public double ExpiresIn { get; set; }
public required UserToken UserToken { get; set; }
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Browl.Service.AuthSecurity.API.Entities;
public class UserToken
{
public required string Id { get; set; }
public required string Email { get; set; }
public required IEnumerable<UserClaim> UserClaims { get; set; }
public string Id { get; set; }
public string Email { get; set; }
public IEnumerable<UserClaim> UserClaims { get; set; }
}
Loading

0 comments on commit a2bad2f

Please sign in to comment.