Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 3 - Separate Auth feature #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions Src/DDD.Infra.CrossCutting.Identity/Services/AuthService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using DDD.Domain.Interfaces;
using DDD.Infra.CrossCutting.Identity.Data;
using DDD.Infra.CrossCutting.Identity.Models;
using DDD.Infra.CrossCutting.Identity.Models.AccountViewModels;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace DDD.Infra.CrossCutting.Identity.Services
{
public class AuthService : IAuthService
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly AuthDbContext _dbContext;
private readonly IJwtFactory _jwtFactory;
private readonly IUser _user;

public AuthService(UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager,
AuthDbContext dbContext,
IJwtFactory jwtFactory,
IUser user)
{
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
_dbContext = dbContext;
_jwtFactory = jwtFactory;
_user = user;
}
public async Task<(TokenViewModel, string)> LoginAsync(string email, string password)
{
var signInResult = await _signInManager.PasswordSignInAsync(email, password, false, true);

if (!signInResult.Succeeded)
{
return (null, signInResult.ToString());
}

var user = await _userManager.FindByEmailAsync(email);

return (await GenerateToken(user), null);
}

public async Task<(bool, IdentityResult)> RegisterAsync(string email, string password)
{
var user = new ApplicationUser { UserName = email, Email = email };
var userCreated = await _userManager.CreateAsync(user,password);
if (!userCreated.Succeeded)
{
return (false, userCreated);
}

var roleAssigned = await _userManager.AddToRoleAsync(user, "Admin");
if (!userCreated.Succeeded)
{
return (false, roleAssigned);
}

var userClaims = new List<Claim>
{
new("Customers_Write", "Write"),
new("Customers_Remove", "Remove"),
};
await _userManager.AddClaimsAsync(user, userClaims);

return (true, null);
}

public async Task<(TokenViewModel, string, string)> RefreshTokenAsync(string accessToken, string refreshToken)
{
var refreshTokenCurrent = await _dbContext.RefreshTokens.SingleOrDefaultAsync
(x => x.Token == refreshToken && !x.Used && !x.Invalidated);

if (refreshTokenCurrent.ExpiryDate < DateTime.UtcNow)
{
refreshTokenCurrent.Invalidated = true;
await _dbContext.SaveChangesAsync();
return (null, "RefreshToken", "Refresh token invalid");
}

// Get User
var appUser = await _userManager.FindByIdAsync(refreshTokenCurrent.UserId);
if (appUser is null)
{
return new (null, "User", "User does not exist");
}

refreshTokenCurrent.Used = true;
await _dbContext.SaveChangesAsync();

return new(await GenerateToken(appUser), null, null);
}

public dynamic GetCurrentUser() =>
new
{
IsAuthenticated = _user.IsAuthenticated(),
ClaimsIdentity = _user.GetClaimsIdentity().Select(x => new { x.Type, x.Value }),
};

private async Task<TokenViewModel> GenerateToken(ApplicationUser appUser)
{
var claimsIdentity = new ClaimsIdentity(await GetClaims(appUser));

var jwtToken = await _jwtFactory.GenerateJwtToken(claimsIdentity);

var refreshToken = new RefreshToken
{
Token = Guid.NewGuid().ToString("N"),
UserId = appUser.Id,
CreationDate = DateTime.UtcNow,
ExpiryDate = DateTime.UtcNow.AddMinutes(90),
JwtId = jwtToken.JwtId
};
await _dbContext.RefreshTokens.AddAsync(refreshToken);
await _dbContext.SaveChangesAsync();

return new TokenViewModel
{
AccessToken = jwtToken.AccessToken,
RefreshToken = refreshToken.Token,
};
}

private async Task<List<Claim>> GetClaims(ApplicationUser user)
{
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Email, user.Email),
new(ClaimTypes.NameIdentifier, user.Id),
};

claims.AddRange(await _userManager.GetClaimsAsync(user));

var userRoles = await _userManager.GetRolesAsync(user);
claims.AddRange(userRoles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role)));

foreach (var userRole in userRoles)
{
var role = await _roleManager.FindByNameAsync(userRole);
var roleClaims = await _roleManager.GetClaimsAsync(role);
claims.AddRange(roleClaims);
}

return claims;
}
}
}
14 changes: 14 additions & 0 deletions Src/DDD.Infra.CrossCutting.Identity/Services/IAuthService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Threading.Tasks;
using DDD.Infra.CrossCutting.Identity.Models.AccountViewModels;
using Microsoft.AspNetCore.Identity;

namespace DDD.Infra.CrossCutting.Identity.Services
{
public interface IAuthService
{
Task<(TokenViewModel, string)> LoginAsync(string email, string password);
Task<(bool, IdentityResult)> RegisterAsync(string email, string password);
Task<(TokenViewModel, string, string)> RefreshTokenAsync(string accessToken, string refreshToken);
dynamic GetCurrentUser();
}
}
163 changes: 19 additions & 144 deletions Src/DDD.Services.Api/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using DDD.Domain.Core.Bus;
using DDD.Domain.Core.Notifications;
using DDD.Domain.Interfaces;
using DDD.Infra.CrossCutting.Identity.Data;
using DDD.Infra.CrossCutting.Identity.Models;
using DDD.Infra.CrossCutting.Identity.Models.AccountViewModels;
using DDD.Infra.CrossCutting.Identity.Services;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

Expand All @@ -22,31 +13,16 @@ namespace DDD.Services.Api.Controllers
[Authorize]
public class AccountController : ApiController
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly AuthDbContext _dbContext;
private readonly IUser _user;
private readonly IJwtFactory _jwtFactory;
private readonly IAuthService _authService;
private readonly ILogger _logger;

public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager,
AuthDbContext dbContext,
IUser user,
IJwtFactory jwtFactory,
IAuthService authService,
ILoggerFactory loggerFactory,
INotificationHandler<DomainNotification> notifications,
IMediatorHandler mediator) : base(notifications, mediator)
{
_userManager = userManager;
_signInManager = signInManager;
_roleManager = roleManager;
_dbContext = dbContext;
_user = user;
_jwtFactory = jwtFactory;
_authService = authService;
_logger = loggerFactory.CreateLogger<AccountController>();
}

Expand All @@ -61,20 +37,17 @@ public async Task<IActionResult> Login([FromBody] LoginViewModel model)
return Response();
}

// Sign In
var signInResult = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, true);
if (!signInResult.Succeeded)
var (token, error) = await _authService.LoginAsync(model.Email, model.Password);

if (token is null)
{
NotifyError(signInResult.ToString(), "Login failure");
NotifyError(error, "Login failure");
return Response();
}

// Get User
var appUser = await _userManager.FindByEmailAsync(model.Email);
//var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);

_logger.LogInformation(1, "User logged in.");
return Response(await GenerateToken(appUser));

return Response(token);
}

[HttpPost]
Expand All @@ -88,34 +61,14 @@ public async Task<IActionResult> Register(RegisterViewModel model)
return Response();
}

// Add User
var appUser = new ApplicationUser { UserName = model.Email, Email = model.Email };
var identityResult = await _userManager.CreateAsync(appUser, model.Password);
if (!identityResult.Succeeded)
var (isRegistrationCompleted, error) = await _authService.RegisterAsync(model.Email, model.Password);

if (!isRegistrationCompleted)
{
AddIdentityErrors(identityResult);
AddIdentityErrors(error);
return Response();
}

// Add UserRoles
identityResult = await _userManager.AddToRoleAsync(appUser, "Admin");
if (!identityResult.Succeeded)
{
AddIdentityErrors(identityResult);
return Response();
}

// Add UserClaims
var userClaims = new List<Claim>
{
new Claim("Customers_Write", "Write"),
new Claim("Customers_Remove", "Remove"),
};
await _userManager.AddClaimsAsync(appUser, userClaims);

// SignIn
//await _signInManager.SignInAsync(user, false);

_logger.LogInformation(3, "User created a new account with password.");
return Response();
}
Expand All @@ -131,97 +84,19 @@ public async Task<IActionResult> Refresh(TokenViewModel model)
return Response();
}

// Get current RefreshToken
var refreshTokenCurrent = _dbContext.RefreshTokens.SingleOrDefault
(x => x.Token == model.RefreshToken && !x.Used && !x.Invalidated);
if (refreshTokenCurrent is null)
{
NotifyError("RefreshToken", "Refresh token does not exist");
return Response();
}
if (refreshTokenCurrent.ExpiryDate < DateTime.UtcNow)
{
// Update current RefreshToken
refreshTokenCurrent.Invalidated = true;
await _dbContext.SaveChangesAsync();
NotifyError("RefreshToken", "Refresh token invalid");
return Response();
}
var (token, errorCode, errorMessage) = await _authService.RefreshTokenAsync(model.AccessToken, model.RefreshToken);

// Get User
var appUser = await _userManager.FindByIdAsync(refreshTokenCurrent.UserId);
if (appUser is null)
if (token is null)
{
NotifyError("User", "User does not exist");
NotifyError(errorCode, errorMessage);
return Response();
}

// Remove current RefreshToken
//_dbContext.Remove(refreshTokenCurrent);
//await _dbContext.SaveChangesAsync();

// Update current RefreshToken
refreshTokenCurrent.Used = true;
await _dbContext.SaveChangesAsync();

return Response(await GenerateToken(appUser));

return Response(token);
}

[HttpGet]
[Route("current")]
public IActionResult GetCurrent()
{
return Response(new
{
IsAuthenticated = _user.IsAuthenticated(),
ClaimsIdentity = _user.GetClaimsIdentity().Select(x => new { x.Type, x.Value }),
});
}

private async Task<TokenViewModel> GenerateToken(ApplicationUser appUser)
{
// Init ClaimsIdentity
var claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(JwtRegisteredClaimNames.Email, appUser.Email));
claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, appUser.Id));

// Get UserClaims
var userClaims = await _userManager.GetClaimsAsync(appUser);
claimsIdentity.AddClaims(userClaims);

// Get UserRoles
var userRoles = await _userManager.GetRolesAsync(appUser);
claimsIdentity.AddClaims(userRoles.Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role)));
// ClaimsIdentity.DefaultRoleClaimType & ClaimTypes.Role is the same

// Get RoleClaims
foreach (var userRole in userRoles)
{
var role = await _roleManager.FindByNameAsync(userRole);
var roleClaims = await _roleManager.GetClaimsAsync(role);
claimsIdentity.AddClaims(roleClaims);
}

// Generate access token
var jwtToken = await _jwtFactory.GenerateJwtToken(claimsIdentity);

// Add refresh token
var refreshToken = new RefreshToken
{
Token = Guid.NewGuid().ToString("N"),
UserId = appUser.Id,
CreationDate = DateTime.UtcNow,
ExpiryDate = DateTime.UtcNow.AddMinutes(90),
JwtId = jwtToken.JwtId
};
await _dbContext.RefreshTokens.AddAsync(refreshToken);
await _dbContext.SaveChangesAsync();

return new TokenViewModel
{
AccessToken = jwtToken.AccessToken,
RefreshToken = refreshToken.Token,
};
}
public IActionResult GetCurrent() => Response(_authService.GetCurrentUser());
}
}
Loading