Feature: Google oauth

This commit is contained in:
Kamigen
2024-05-08 19:04:25 -04:00
parent cd2bf64af5
commit bbbfddd6cb
5 changed files with 143 additions and 84 deletions

View File

@@ -0,0 +1,54 @@
using System.Security.Claims;
using Google.Apis.Oauth2.v2.Data;
using Hutopy.Domain.Interfaces;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Mvc;
namespace Hutopy.Web.Controllers;
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"),
});
}
public async Task<IActionResult> Callback()
{
var authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
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
};
await userService.CreateUserAsync(userInfo); // TODO: Don't create user if already exists
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new(ClaimTypes.Name, userInfo.Name),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.GivenName, userInfo.GivenName),
new(ClaimTypes.Surname, userInfo.FamilyName)
}, CookieAuthenticationDefaults.AuthenticationScheme)));
return Redirect("/");
}
}

View File

@@ -1,26 +0,0 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Mvc;
namespace Hutopy.Web.Endpoints;
public class Google : EndpointGroupBase
{
public override void Map(WebApplication app)
{
app.MapGroup(this)
.MapGet("/o/sign-in", Callback);
}
private static async Task<IActionResult> Callback(ISender sender, HttpContext context)
{
var properties = new AuthenticationProperties
{
RedirectUri = "/signin-google", ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30),
};
await context.ChallengeAsync(GoogleDefaults.AuthenticationScheme, properties);
return new ChallengeResult(GoogleDefaults.AuthenticationScheme, properties);
}
}

View File

@@ -5,7 +5,11 @@ using Hutopy.Infrastructure.Data;
using Hutopy.Infrastructure.Services;
using Hutopy.Web;
using Azure.Identity;
using Hutopy.Infrastructure.Identity;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
var builder = WebApplication.CreateBuilder(args);
@@ -49,40 +53,43 @@ builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddWebServices();
// OAuth
builder.Services.AddAuthorization();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "Hutopy";
options.Cookie.SecurePolicy =
builder.Environment.IsDevelopment() ? CookieSecurePolicy.None : CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.MaxAge = TimeSpan.FromDays(30);
})
.AddGoogle(options =>
.AddCookie()
.AddGoogle(
GoogleDefaults.AuthenticationScheme,
options =>
{
options.ClientId = builder.Configuration["Google:ClientId"] ??
throw new ArgumentNullException("The Google ClientId is missing.");
options.ClientSecret = builder.Configuration["Google:ClientSecret"] ??
throw new ArgumentNullException("The Google ClientSecret is missing.");
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Events.OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri + "&prompt=consent");
return Task.CompletedTask;
};
});
// Password hashing
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = true;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddControllers();
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
app.UseForwardedHeaders(
new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedProto }
);
app.UseAuthentication();
app.UseAuthorization();

View File

@@ -26,27 +26,6 @@
}
}
},
"/api/Google/o/sign-in": {
"get": {
"tags": [
"Google"
],
"operationId": "GetApiGoogleOSignIn",
"responses": {
"200": {
"description": "",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
},
"/api/JoinUs": {
"get": {
"tags": [
@@ -628,6 +607,19 @@
}
]
}
},
"/api/google/sign-in": {
"get": {
"tags": [
"Google"
],
"operationId": "Google_SignIn",
"responses": {
"200": {
"description": ""
}
}
}
}
},
"components": {