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