#oauth changed GoogleController for the jwt flow ( using a common token if we connect from our app or from google )

This commit is contained in:
Dominic Villemure
2024-06-09 23:44:37 -04:00
parent ac87aeb4c4
commit 6f76cb2084
16 changed files with 338 additions and 842 deletions

View File

@@ -48,13 +48,20 @@ public static class DependencyInjection
.AddBearerToken(IdentityConstants.BearerScheme);
services.AddAuthorizationBuilder();
// Might need to change and use AddIdentity<User, Role>() when we need to integrate connection via third party ( facebook, google )
services
.AddIdentityCore<ApplicationUser>()
services.AddIdentityCore<ApplicationUser>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 8;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddApiEndpoints();
.AddApiEndpoints()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddDefaultTokenProviders();
services.AddSingleton(TimeProvider.System);
services.AddScoped<IIdentityService, IdentityService>();

View File

@@ -2,17 +2,21 @@ using Google.Apis.Oauth2.v2.Data;
using System.Security.Claims;
using Hutopy.Application.Common.Interfaces;
using Hutopy.Application.Common.Models;
using Hutopy.Infrastructure.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
namespace Hutopy.Infrastructure.Identity;
public class IdentityService(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory,
IAuthorizationService authorizationService,
IHttpContextAccessor contextAccessor
IHttpContextAccessor contextAccessor,
IConfiguration configuration
)
: IIdentityService
{
@@ -205,4 +209,23 @@ public class IdentityService(
return userRoles;
}
public async Task<string?> LoginAsync(string userName, string password)
{
var result = await signInManager.PasswordSignInAsync(userName, password, isPersistent: false, lockoutOnFailure: false);
if (!result.Succeeded)
{
return null;
}
var user = await GetUserByUserNameAsync(userName);
var token = JwtTokenHelper.GenerateJwtToken(
issuer: configuration["Jwt:Issuer"] ?? "",
audience: configuration["Jwt:Audience"] ?? "",
key: configuration["Jwt:Key"] ?? "",
userId: user?.Id ?? "");
return token;
}
}

View File

@@ -1,134 +0,0 @@
using System.Text;
using System.Security.Claims;
using Hutopy.Domain.Interfaces;
using Hutopy.Domain.Models;
using Hutopy.Infrastructure.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
namespace Hutopy.Infrastructure.Services;
public class UserService(UserManager<ApplicationUser> userManager, IHttpContextAccessor contextAccessor) : IUserService
{
public async Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password)
{
var applicationUser = new ApplicationUser
{
UserName = userName,
Email = email,
FirstName = firstName,
LastName = lastName
};
//todo: Need to handle errors better for the user.
var response = await userManager.CreateAsync(applicationUser, password);
if (response.Errors.Any())
{
throw new Exception("Failed to create user", new AggregateException(response.Errors.Select(e => new Exception(e.Description))));
}
}
public async Task<UserModel?> FindUserByIdAsync(string id)
{
var response = await userManager.FindByIdAsync(id);
if (response == null) return null;
var userModel = new UserModel
{
Id = response.Id,
UserName = response.UserName,
FirstName = response.FirstName,
LastName = response.LastName,
Email = response.Email,
};
return userModel;
}
public async Task<UserModel?> GetCurrentUserAsync()
{
// todo: Get the id of the user doing the request.
var currentUserId = contextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (string.IsNullOrEmpty(currentUserId))
{
return null;
}
return await FindUserByIdAsync(currentUserId);
}
public async Task<UserModel?> FindUserByEmailAsync(string email)
{
var response = await userManager.FindByEmailAsync(email);
if (response == null) return null;
var userModel = new UserModel
{
Id = response.Id,
UserName = response.UserName,
FirstName = response.FirstName,
LastName = response.LastName,
Email = response.Email
};
return userModel;
}
}
// If we need to add special characters we can alternate between 2 pools.
public class RandomGenerator
{
// For the moment, numbers and special characters don't work because
// the random generator is designed to handle a single integer.
// We can modify this in the future.
private const string LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "!@#$%^&*()_+"
+ "-=[];',./`~{}|:\"<>?";
private const int LetterIdxBits = 6;
private const int LetterIdxMask = 1 << LetterIdxBits;
private const int LetterIdxMax = 64 / LetterIdxBits;
private static readonly Random Src = new();
public static byte[] RandBytesMaskSrc(int n)
{
var b = new byte[n];
for (var i = n - 1; i >= 0;)
{
long cache = Src.NextInt64();
int remain = LetterIdxMax;
while (remain != 0)
{
if (i < 0)
break;
if (cache == 0)
cache = Src.NextInt64();
var idx = (int)(cache & LetterIdxMask);
if (idx < LetterBytes.Length)
{
b[i] = (byte)LetterBytes[idx];
i--;
}
cache >>= LetterIdxBits;
remain--;
}
}
return b;
}
public static string RandomString(int length)
{
var bytes = RandBytesMaskSrc(length);
return Encoding.UTF8.GetString(bytes); // Equivalent for *(string*)(&bytes[0])
}
}

View File

@@ -0,0 +1,31 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace Hutopy.Infrastructure.Utils;
public static class JwtTokenHelper
{
public static string GenerateJwtToken(string issuer, string audience, string key, string userId)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, userId)
};
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: issuer,
audience: audience,
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}

View File

@@ -0,0 +1,58 @@
using System.Text;
namespace Hutopy.Infrastructure.Utils;
// If we need to add special characters we can alternate between 2 pools.
public class RandomGenerator
{
// For the moment, numbers and special characters don't work because
// the random generator is designed to handle a single integer.
// We can modify this in the future.
private const string LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "!@#$%^&*()_+"
+ "-=[];',./`~{}|:\"<>?";
private const int LetterIdxBits = 6;
private const int LetterIdxMask = 1 << LetterIdxBits;
private const int LetterIdxMax = 64 / LetterIdxBits;
private static readonly Random Src = new();
public static byte[] RandBytesMaskSrc(int n)
{
var b = new byte[n];
for (var i = n - 1; i >= 0;)
{
long cache = Src.NextInt64();
int remain = LetterIdxMax;
while (remain != 0)
{
if (i < 0)
break;
if (cache == 0)
cache = Src.NextInt64();
var idx = (int)(cache & LetterIdxMask);
if (idx < LetterBytes.Length)
{
b[i] = (byte)LetterBytes[idx];
i--;
}
cache >>= LetterIdxBits;
remain--;
}
}
return b;
}
public static string RandomString(int length)
{
var bytes = RandBytesMaskSrc(length);
return Encoding.UTF8.GetString(bytes); // Equivalent for *(string*)(&bytes[0])
}
}