From aa8a5ad93d52295b3a318ae40f1fe1c4d117100a Mon Sep 17 00:00:00 2001 From: Kamigen <46357922+Edouard127@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:01:44 -0400 Subject: [PATCH] Feature: Google and Facebook sign-in --- src/Domain/Interfaces/IUserService.cs | 5 +- src/Infrastructure/Services/UserService.cs | 6 --- src/Web/Controllers/FacebookController.cs | 53 ++++++++++++++++++++++ src/Web/Controllers/GoogleController.cs | 39 +++++++--------- src/Web/Program.cs | 14 +++++- src/Web/wwwroot/api/specification.json | 13 ++++++ 6 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 src/Web/Controllers/FacebookController.cs diff --git a/src/Domain/Interfaces/IUserService.cs b/src/Domain/Interfaces/IUserService.cs index 2af0f97..efe46bb 100644 --- a/src/Domain/Interfaces/IUserService.cs +++ b/src/Domain/Interfaces/IUserService.cs @@ -1,5 +1,4 @@ -using Google.Apis.Oauth2.v2.Data; -using Hutopy.Domain.Models; +using Hutopy.Domain.Models; namespace Hutopy.Domain.Interfaces; @@ -7,8 +6,6 @@ public interface IUserService { Task CreateUserAsync(string email, string userName, string firstName, string lastName, string password); - Task CreateUserAsync(Userinfo userInfo); - Task FindUserByIdAsync(string id); Task GetCurrentUserAsync(); diff --git a/src/Infrastructure/Services/UserService.cs b/src/Infrastructure/Services/UserService.cs index 8610844..4e52033 100644 --- a/src/Infrastructure/Services/UserService.cs +++ b/src/Infrastructure/Services/UserService.cs @@ -1,5 +1,4 @@ using System.Text; -using Google.Apis.Oauth2.v2.Data; using System.Security.Claims; using Hutopy.Domain.Interfaces; using Hutopy.Domain.Models; @@ -30,11 +29,6 @@ public class UserService(UserManager userManager, IHttpContextA } } - public async Task CreateUserAsync(Userinfo userInfo) - { - await CreateUserAsync(userInfo.Email, userInfo.GivenName, userInfo.GivenName, userInfo.FamilyName, RandomGenerator.RandomString(24)); - } - public async Task FindUserByIdAsync(string id) { var response = await userManager.FindByIdAsync(id); diff --git a/src/Web/Controllers/FacebookController.cs b/src/Web/Controllers/FacebookController.cs new file mode 100644 index 0000000..394eea1 --- /dev/null +++ b/src/Web/Controllers/FacebookController.cs @@ -0,0 +1,53 @@ +using System.Security.Claims; +using Hutopy.Domain.Interfaces; +using Hutopy.Infrastructure.Services; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.Facebook; +using Microsoft.AspNetCore.Mvc; + +namespace Hutopy.Web.Controllers; + +public class FacebookController(IUserService userService) : Controller +{ + [HttpGet("/api/facebook/sign-in")] + public async Task SignIn() + { + await HttpContext.ChallengeAsync(FacebookDefaults.AuthenticationScheme, new AuthenticationProperties + { + RedirectUri = Url.Action("Authorize") + }); + } + + public async Task Authorize() + { + var authenticateResult = await HttpContext.AuthenticateAsync(FacebookDefaults.AuthenticationScheme); + + if (!authenticateResult.Succeeded) return BadRequest(); + + var claims = authenticateResult.Principal.Claims.ToList(); + + var name = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; + var email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + var givenName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value; + var familyName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value; + + var claimsIdentity = new ClaimsIdentity(new List + { + new(ClaimTypes.Name, name), + new(ClaimTypes.Email, email), + new(ClaimTypes.GivenName, givenName), + new(ClaimTypes.Surname, familyName) + }, CookieAuthenticationDefaults.AuthenticationScheme); + + if (await userService.FindUserByEmailAsync(email) != null) + { + await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); + return Redirect("/"); + } + + await userService.CreateUserAsync(email, givenName, givenName, familyName, RandomGenerator.RandomString(24)); + await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); + return Redirect("/"); + } +} diff --git a/src/Web/Controllers/GoogleController.cs b/src/Web/Controllers/GoogleController.cs index 236f130..0780277 100644 --- a/src/Web/Controllers/GoogleController.cs +++ b/src/Web/Controllers/GoogleController.cs @@ -1,6 +1,6 @@ using System.Security.Claims; -using Google.Apis.Oauth2.v2.Data; using Hutopy.Domain.Interfaces; +using Hutopy.Infrastructure.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Google; @@ -8,52 +8,45 @@ using Microsoft.AspNetCore.Mvc; namespace Hutopy.Web.Controllers; -public class GoogleController( - IUserService userService) : Controller +public class GoogleController(IUserService userService) : Controller { [HttpGet("/api/google/sign-in")] public async Task SignIn() { await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme, new AuthenticationProperties { - RedirectUri = Url.Action("Callback"), + RedirectUri = Url.Action("Authorize") }); } - public async Task Callback() + public async Task Authorize() { - var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); + var authenticateResult = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); - if (!authenticateResult.Succeeded) - { - return BadRequest(); - } + if (!authenticateResult.Succeeded) return BadRequest(); var claims = authenticateResult.Principal.Claims.ToList(); - var userInfo = new Userinfo - { - Name = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value, - Email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value, - GivenName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value, - FamilyName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value - }; + var name = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; + var email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value; + var givenName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value; + var familyName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value; var claimsIdentity = new ClaimsIdentity(new List { - new(ClaimTypes.Name, userInfo.Name), - new(ClaimTypes.Email, userInfo.Email), - new(ClaimTypes.GivenName, userInfo.GivenName), - new(ClaimTypes.Surname, userInfo.FamilyName) + new(ClaimTypes.Name, name), + new(ClaimTypes.Email, email), + new(ClaimTypes.GivenName, givenName), + new(ClaimTypes.Surname, familyName) }, CookieAuthenticationDefaults.AuthenticationScheme); - if (await userService.FindUserByEmailAsync(userInfo.Email) != null) // TODO: Do we need to check for null ? + if (await userService.FindUserByEmailAsync(email) != null) { await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); return Redirect("/"); } - await userService.CreateUserAsync(userInfo); + await userService.CreateUserAsync(email, givenName, givenName, familyName, RandomGenerator.RandomString(24)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); return Redirect("/"); } diff --git a/src/Web/Program.cs b/src/Web/Program.cs index c49790c..45dfcf2 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -7,6 +7,7 @@ using Hutopy.Web; using Azure.Identity; using Hutopy.Infrastructure.Identity; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.Facebook; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; @@ -56,7 +57,7 @@ builder.Services.AddWebServices(); builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie() .AddGoogle( @@ -67,6 +68,17 @@ builder.Services.AddAuthentication(options => throw new ArgumentNullException("The Google ClientId is missing."); options.ClientSecret = builder.Configuration["Google:ClientSecret"] ?? throw new ArgumentNullException("The Google ClientSecret is missing."); + //options.AccessDeniedPath = "/AccessDeniedPathInfo"; + }) + .AddFacebook( + FacebookDefaults.AuthenticationScheme, + options => + { + options.ClientId = builder.Configuration["Facebook:ClientId"] ?? + throw new ArgumentNullException("The Facebook ClientId is missing."); + options.ClientSecret = builder.Configuration["Facebook:ClientSecret"] ?? + throw new ArgumentNullException("The Facebook ClientSecret is missing."); + //options.AccessDeniedPath = "/AccessDeniedPathInfo"; }); // Password hashing diff --git a/src/Web/wwwroot/api/specification.json b/src/Web/wwwroot/api/specification.json index b5a4c09..a6129af 100644 --- a/src/Web/wwwroot/api/specification.json +++ b/src/Web/wwwroot/api/specification.json @@ -685,6 +685,19 @@ ] } }, + "/api/facebook/sign-in": { + "get": { + "tags": [ + "Facebook" + ], + "operationId": "Facebook_SignIn", + "responses": { + "200": { + "description": "" + } + } + } + }, "/api/google/sign-in": { "get": { "tags": [