Feature: Google and Facebook sign-in

This commit is contained in:
Kamigen
2024-06-05 16:01:44 -04:00
parent fc0c94306b
commit aa8a5ad93d
6 changed files with 96 additions and 34 deletions

View File

@@ -1,5 +1,4 @@
using Google.Apis.Oauth2.v2.Data; using Hutopy.Domain.Models;
using Hutopy.Domain.Models;
namespace Hutopy.Domain.Interfaces; 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(string email, string userName, string firstName, string lastName, string password);
Task CreateUserAsync(Userinfo userInfo);
Task<UserModel?> FindUserByIdAsync(string id); Task<UserModel?> FindUserByIdAsync(string id);
Task<UserModel?> GetCurrentUserAsync(); Task<UserModel?> GetCurrentUserAsync();

View File

@@ -1,5 +1,4 @@
using System.Text; using System.Text;
using Google.Apis.Oauth2.v2.Data;
using System.Security.Claims; using System.Security.Claims;
using Hutopy.Domain.Interfaces; using Hutopy.Domain.Interfaces;
using Hutopy.Domain.Models; using Hutopy.Domain.Models;
@@ -30,11 +29,6 @@ public class UserService(UserManager<ApplicationUser> userManager, IHttpContextA
} }
} }
public async Task CreateUserAsync(Userinfo userInfo)
{
await CreateUserAsync(userInfo.Email, userInfo.GivenName, userInfo.GivenName, userInfo.FamilyName, RandomGenerator.RandomString(24));
}
public async Task<UserModel?> FindUserByIdAsync(string id) public async Task<UserModel?> FindUserByIdAsync(string id)
{ {
var response = await userManager.FindByIdAsync(id); var response = await userManager.FindByIdAsync(id);

View File

@@ -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<IActionResult> 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<Claim>
{
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("/");
}
}

View File

@@ -1,6 +1,6 @@
using System.Security.Claims; using System.Security.Claims;
using Google.Apis.Oauth2.v2.Data;
using Hutopy.Domain.Interfaces; using Hutopy.Domain.Interfaces;
using Hutopy.Infrastructure.Services;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.Google;
@@ -8,52 +8,45 @@ using Microsoft.AspNetCore.Mvc;
namespace Hutopy.Web.Controllers; namespace Hutopy.Web.Controllers;
public class GoogleController( public class GoogleController(IUserService userService) : Controller
IUserService userService) : Controller
{ {
[HttpGet("/api/google/sign-in")] [HttpGet("/api/google/sign-in")]
public async Task SignIn() public async Task SignIn()
{ {
await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme, new AuthenticationProperties await HttpContext.ChallengeAsync(GoogleDefaults.AuthenticationScheme, new AuthenticationProperties
{ {
RedirectUri = Url.Action("Callback"), RedirectUri = Url.Action("Authorize")
}); });
} }
public async Task<IActionResult> Callback() public async Task<IActionResult> Authorize()
{ {
var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); var authenticateResult = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);
if (!authenticateResult.Succeeded) if (!authenticateResult.Succeeded) return BadRequest();
{
return BadRequest();
}
var claims = authenticateResult.Principal.Claims.ToList(); var claims = authenticateResult.Principal.Claims.ToList();
var userInfo = new Userinfo var name = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
{ var email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
Name = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value, var givenName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
Email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value, var familyName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
GivenName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value,
FamilyName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value
};
var claimsIdentity = new ClaimsIdentity(new List<Claim> var claimsIdentity = new ClaimsIdentity(new List<Claim>
{ {
new(ClaimTypes.Name, userInfo.Name), new(ClaimTypes.Name, name),
new(ClaimTypes.Email, userInfo.Email), new(ClaimTypes.Email, email),
new(ClaimTypes.GivenName, userInfo.GivenName), new(ClaimTypes.GivenName, givenName),
new(ClaimTypes.Surname, userInfo.FamilyName) new(ClaimTypes.Surname, familyName)
}, CookieAuthenticationDefaults.AuthenticationScheme); }, 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)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
return Redirect("/"); return Redirect("/");
} }
await userService.CreateUserAsync(userInfo); await userService.CreateUserAsync(email, givenName, givenName, familyName, RandomGenerator.RandomString(24));
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
return Redirect("/"); return Redirect("/");
} }

View File

@@ -7,6 +7,7 @@ using Hutopy.Web;
using Azure.Identity; using Azure.Identity;
using Hutopy.Infrastructure.Identity; using Hutopy.Infrastructure.Identity;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Facebook;
using Microsoft.AspNetCore.Authentication.Google; using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@@ -56,7 +57,7 @@ builder.Services.AddWebServices();
builder.Services.AddAuthentication(options => builder.Services.AddAuthentication(options =>
{ {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}) })
.AddCookie() .AddCookie()
.AddGoogle( .AddGoogle(
@@ -67,6 +68,17 @@ builder.Services.AddAuthentication(options =>
throw new ArgumentNullException("The Google ClientId is missing."); throw new ArgumentNullException("The Google ClientId is missing.");
options.ClientSecret = builder.Configuration["Google:ClientSecret"] ?? options.ClientSecret = builder.Configuration["Google:ClientSecret"] ??
throw new ArgumentNullException("The Google ClientSecret is missing."); 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 // Password hashing

View File

@@ -685,6 +685,19 @@
] ]
} }
}, },
"/api/facebook/sign-in": {
"get": {
"tags": [
"Facebook"
],
"operationId": "Facebook_SignIn",
"responses": {
"200": {
"description": ""
}
}
}
},
"/api/google/sign-in": { "/api/google/sign-in": {
"get": { "get": {
"tags": [ "tags": [