Executando verificação de segurança...
1

O .NET tem o identity que é um sistema EXTREMAMENTE poderoso.

Com poucas linhas de código vc já tem o backend de autorização inteiro.

Porque usar uma ferramenta externa que só vai trazer dor de cabeça?

Imagina cada request Http ter que bater no keycloak para validar? isso é o que? 40ms a mais A CADA REQUEST, sendo que uma consulta no DB vai aumentar no máximo 1ms (se for necessário consultar no DB)


No DB Context:

AppDbContext : IdentityDbContext<User, Role, Guid>
AppDbContext : IdentityDbContext<IdentityUser<Guid>, IdentityRole<Guid>, Guid> // Se não precisar modificar

No program.cs:

services.AddIdentity<User, Role>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredLength = 8;

    options.User.RequireUniqueEmail = true;

    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    options.SignIn.RequireConfirmedEmail = false;
    options.SignIn.RequireConfirmedPhoneNumber = false;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

services.ConfigureApplicationCookie(options =>
{
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = environment.IsDevelopment() 
        ? CookieSecurePolicy.SameAsRequest 
        : CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.Cookie.Name = "Auth";
    options.Cookie.Path = "/";
    options.ExpireTimeSpan = TimeSpan.FromDays(7);
    options.SlidingExpiration = true;

    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    };

    options.Events.OnRedirectToAccessDenied = context =>
    {
        context.Response.StatusCode = StatusCodes.Status403Forbidden;
        return Task.CompletedTask;
    };
});

Services.AddAuthorization();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

É só isso e está funcionando!

Não precisa de serviço externo,
Não precisa inventar a roda

Melhor ainda?

Toma o serviço de autenticação pronto pra ti:

internal class AuthenticationService : IAuthenticationService
{
    private readonly UserManager<User> userManager;
    private readonly SignInManager<User> signInManager;
    private readonly AppDbContext context;

    public AuthenticationService(
        UserManager<User> userManager,
        SignInManager<User> signInManager,
        AppDbContext context)
    {
        this.userManager = userManager;
        this.signInManager = signInManager;
        this.context = context;
    }

    public async Task<(bool Success, string? Message, User? User)> Login(
        string identifier,
        string password,
        bool rememberMe,
        CancellationToken cancellationToken = default)
    {
        var user = await userManager.Users
            .FirstOrDefaultAsync(u => 
                u.Email == identifier || u.NormalizedEmail == identifier.ToUpper(), 
                cancellationToken);

        if (user == null)
        {
            return (false, "The email or password you entered is incorrect. Please try again.", null);
        }

        var result = await signInManager.PasswordSignInAsync(
            user,
            password,
            rememberMe,
            lockoutOnFailure: true);

        if (!result.Succeeded)
        {
            if (result.IsLockedOut)
            {
                return (false, "Your account has been locked due to multiple failed login attempts. Please try again later.", null);
            }
            return (false, "The email or password you entered is incorrect. Please try again.", null);
        }

        await context.Entry(user)
            .LoadAsync(cancellationToken);

        return (true, "Login successful", user);
    }

    public async Task Logout(CancellationToken cancellationToken = default)
    {
        await signInManager.SignOutAsync();
    }

    public Task<User?> GetCurrentUser(Guid userId, CancellationToken cancellationToken = default)
    {
        return userManager.Users
            .AsNoTracking()
            .Where(u => u.Id == userId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    public async Task<User> RegisterUser(
        string email,
        string password,
        string firstName,
        string lastName,
        string? phoneNumber,
        Guid? organizationId,
        CancellationToken cancellationToken = default)
    {
        var existingEmailUser = await userManager.FindByEmailAsync(email);
        if (existingEmailUser != null)
        {
            throw new Exception("Email is already registered");
        }

        var user = new User
        {
            Id = Guid.NewGuid(),
            UserName = email,
            Email = email,
            FirstName = firstName,
            LastName = lastName,
            EmailConfirmed = false
        };

        var result = await userManager.CreateAsync(user, password);

        if (!result.Succeeded)
        {
            throw new Exception(string.Join(", ", result.Errors.Select(e => e.Description)));
        }

        await signInManager.SignInAsync(user, isPersistent: false);

        return user;
    }

    public async Task<bool> AddUserToRole(Guid userId, string roleName, CancellationToken cancellationToken = default)
    {
        var user = await userManager.FindByIdAsync(userId.ToString());

        if (user == null)
            return false;

        var result = await userManager.AddToRoleAsync(user, roleName);
        return result.Succeeded;
    }

    public async Task<IEnumerable<string>> GetUserRoles(Guid userId, CancellationToken cancellationToken = default)
    {
        var user = await userManager.FindByIdAsync(userId.ToString());

        if (user == null)
            return Enumerable.Empty<string>();

        return await userManager.GetRolesAsync(user);
    }

    private async Task<bool> EnsureUserClaims(User user)
    {
        //// Exemplo de como colocar informações personalizadas do usuário no cookie
        var existingClaims = await userManager.GetClaimsAsync(user);
        var claimsToAdd = new List<Claim>();

        if (user.OrganizationId.HasValue && user.OrganizationId.Value != Guid.Empty)
        {
            var orgClaim = existingClaims.FirstOrDefault(c => c.Type == "OrganizationId");
            if (orgClaim == null)
            {
                claimsToAdd.Add(new Claim("OrganizationId", user.OrganizationId.ToString()!));
            }
        }

        if (claimsToAdd.Count > 0)
        {
            await userManager.AddClaimsAsync(user, claimsToAdd);
            await signInManager.RefreshSignInAsync(user);
            return true;
        }

        return false;
    }
}

Aceito um pix de presente por te dar toda a autenticação pronta hahahah

Carregando publicação patrocinada...