Skip to content

Commit

Permalink
feat: adding Google authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
raffreitas committed Dec 30, 2024
1 parent 543e1fd commit 2c8c0eb
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 4 deletions.
26 changes: 25 additions & 1 deletion src/MyRecipes.API/Controllers/LoginController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Mvc;

using MyRecipes.Application.UseCases.Login.DoLogin;
using MyRecipes.Application.UseCases.Login.External;
using MyRecipes.Communication.Requests;
using MyRecipes.Communication.Responses;

Expand All @@ -19,4 +25,22 @@ public async Task<IActionResult> Login(

return Ok(response);
}

[HttpGet("google")]
public async Task<IActionResult> GoogleLogin(string returnUrl, [FromServices] IExternalLoginUseCase useCase)
{
var authenticate = await Request.HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme);

if (IsNotAuthenticated(authenticate))
return Challenge(GoogleDefaults.AuthenticationScheme);

var claims = authenticate.Principal!.Identities.First().Claims;

var name = claims.First(c => c.Type == ClaimTypes.Name).Value;
var email = claims.First(c => c.Type == ClaimTypes.Email).Value;

var token = await useCase.Execute(name, email);

return Redirect($"{returnUrl}/{token}");
}
}
9 changes: 8 additions & 1 deletion src/MyRecipes.API/Controllers/MyRecipesBaseController.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;

namespace MyRecipes.API.Controllers;

[ApiController]
public class MyRecipesBaseController : ControllerBase
{
protected static bool IsNotAuthenticated(AuthenticateResult authenticate)
{
return !authenticate.Succeeded
|| authenticate.Principal is null
|| !authenticate.Principal.Identities.Any(id => id.IsAuthenticated);
}
}
3 changes: 2 additions & 1 deletion src/MyRecipes.API/MyRecipes.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="Scalar.AspNetCore" Version="1.2.55" />
<PackageReference Include="Scalar.AspNetCore" Version="1.2.72" />
</ItemGroup>

<ItemGroup>
Expand Down
19 changes: 19 additions & 0 deletions src/MyRecipes.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.AspNetCore.Authentication.Cookies;

using MyRecipes.API.BackgroudServices;
using MyRecipes.API.Converters;
using MyRecipes.API.Filters;
Expand Down Expand Up @@ -36,6 +38,8 @@
if (!builder.Configuration.IsUnitTestEnvironment())
{
builder.Services.AddHostedService<DeleteUserService>();

AddGoogleAuthentication();
}

var app = builder.Build();
Expand Down Expand Up @@ -69,6 +73,21 @@ void MigrateDatabase()
DatabaseMigration.Migrate(connectionString, serviceScope.ServiceProvider);
}

void AddGoogleAuthentication()
{
var clientId = builder.Configuration.GetValue<string>("Settings:Google:ClientId");
var clientSecret = builder.Configuration.GetValue<string>("Settings:Google:ClientSecret");

builder.Services
.AddAuthentication(config => config.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddGoogle(options =>
{
options.ClientId = clientId!;
options.ClientSecret = clientSecret!;
});
}

public partial class Program
{
protected Program() { }
Expand Down
4 changes: 4 additions & 0 deletions src/MyRecipes.API/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
},
"BlobStorage": {
"Azure": ""
},
"Google": {
"ClientId": "",
"ClientSecret": ""
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using MyRecipes.Domain.Repositories;
using MyRecipes.Domain.Repositories.User;
using MyRecipes.Domain.Security.Tokens;

namespace MyRecipes.Application.UseCases.Login.External;

public class ExternalLoginUseCase : IExternalLoginUseCase
{
private readonly IUserReadOnlyRepository _userReadOnlyRepository;
private readonly IUserWriteOnlyRepository _userWriteOnlyRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IAccessTokenGenerator _accessTokenGenerator;

public ExternalLoginUseCase(
IUserReadOnlyRepository userReadOnlyRepository,
IUserWriteOnlyRepository userWriteOnlyRepository,
IUnitOfWork unitOfWork,
IAccessTokenGenerator accessTokenGenerator)
{
_userReadOnlyRepository = userReadOnlyRepository;
_userWriteOnlyRepository = userWriteOnlyRepository;
_unitOfWork = unitOfWork;
_accessTokenGenerator = accessTokenGenerator;
}

public async Task<string> Execute(string name, string email)
{
var user = await _userReadOnlyRepository.GetByEmail(email);

if (user is null)
{
user = new Domain.Entities.User
{
Name = name,
Email = email,
Password = "-"
};

await _userWriteOnlyRepository.Add(user);
await _unitOfWork.Commit();
}

return _accessTokenGenerator.Generate(user.UserIdentifier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace MyRecipes.Application.UseCases.Login.External;
public interface IExternalLoginUseCase
{
Task<string> Execute(string name, string email);
}
2 changes: 1 addition & 1 deletion src/MyRecipes.Domain/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ public class User : EntityBase
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public Guid UserIdentifier { get; set; }
public Guid UserIdentifier { get; set; } = Guid.NewGuid();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ public interface IUserReadOnlyRepository
public Task<bool> ExistsActiveUserWithWithEmail(string email);
public Task<bool> ExistsActiveUserWithIdentifier(Guid userIdentifier);
public Task<Entities.User?> GetByEmailAndPassword(string email, string password);
public Task<Entities.User?> GetByEmail(string email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,11 @@ public async Task DeleteAccount(Guid userIdentifier)
_dbContext.Recipes.RemoveRange(recipes);
_dbContext.Users.Remove(user);
}

public Task<User?> GetByEmail(string email)
{
return _dbContext.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Active && u.Email.Equals(email));
}
}

0 comments on commit 2c8c0eb

Please sign in to comment.