Criando uma api para autenticação de frontends com .NET — parte 1
Desenvolvedores frontend sempre se deparam com desafios na hora de fazer login em aplicações Angular, React, Vue, ou simplesmente javascript, pois isso depende do backend, muitas vezes não tem nenhuma ferramenta destas disponível para utilizar.
Numa aula que ministrei desenvolvi uma aplicação básica em .NET para ajudar aos alunos a realizarem os processos necessários para integrarem as aplicações que fizeram na aula de outro professor responsável pelo frontend.
Então resolvi colocar aqui o exemplo e disponibilizar no github seu fonte, caso mais alguém tenha a necessidade de utilizar nas suas aplicações, já serve como base para começar….
O Projeto
Para estas funcionalidades, precisaremos de uma Web Api, que será feita no .NET 9 e iremos utilizar algumas bibliotecas para incluir as funcionalidades de autenticação.
Então para começar, vamos abrir o Visual Studio e criar um novo projeto:
Depois de escolhido o modelo, dê o nome do projeto e escolha uma pasta
Depois defina as configurações da api na próxima tela
Feito isso, agora o Visual Studio abrirá com o seu projeto seguindo o template padrão do .NET 9.
O template do projeto possui um exemplo que nós não vamos utilizar, portanto podemos marcar estes itens no program.cs e excluir, ficando seu projeto deste jeito agora:
Primeiramente, teremos que incluir algumas bibliotecas que não estão presentes na aplicação, podemos fazer via linha de comando ou através do Nuget Package Manager.
São elas:
Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.AspNetCore.Identity.UI
Microsoft.AspNetCore.OpenApi
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.SqlServer
O código .NET
Feita a preparação da base da aplicação, vamos montar os endpoints base para o processo de gestão de usuários e permissões:
Criando as pastas
Criação de pastas: crie as pastas “Models”, “Data” e “Endpoints”.
Criando as classes
Precisamos criar algumas classes, para poder realizar as requisições para a api, elas devem ser criadas dentro da pasta “Models” criada no passo anterior:
RegisterRequestModel — classe utilizada para registrar um novo usuário
public class RegisterRequestModel
{
[Required]
public string Username { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
[Compare("Password", ErrorMessage = "As senhas não coincidem.")]
public string ConfirmPassword { get; set; }
[Required]
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; } = string.Empty;
}LoginRequestModel — classe utilizada no envio do login
public class LoginRequestModel
{
public required string Email { get; set; }
public required string Password { get; set; }
}UserResponseModel — classe para retorno de informações do usuário, notem que o construtor está recebendo um “Identity User” que é a classe utilizada na gestão de usuários pelo Asp Net Identity, mas por que? para facilitar a criação da entidade que será uma resposta, para não enviar a classe “IdentityUser” que possui outras informações que não deverão ser compartilhadas fora da aplicação.
public class UserResponseModel
{
public UserResponseModel(IdentityUser user)
{
this.Id = user.Id;
this.Email = user.Email;
this.UserName = user.UserName;
}
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string Phone { get; set; } = string.Empty;
}Criando o Contexto
Para que possamos utilizar Entity Framework para armazenar nossos dados, será preciso criar uma classe de contexto, esta classe é responsável pelo acesso ao banco de dados.
Ela é utilizada para gerar também as migrations do banco de dados
Na pasta “Data”, insira uma nova classe com o nome “AppDbContext” e complemente seu código deixando assim:
namespace UserManagement_Api.Data;
public class AppDbContext : IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}O que está acontecendo? Ela herda o modelo de “IdentityDbContext” que é uma implementação utilizada pelo Identity Server (gerenciador de identidades) do .NET, e que fará com que ele já possua métodos prontos para instanciar e manter no banco de dados as entidades de identificação, que serão utilizadas para gerenciar os usuários e acessos.
Criando os Endpoints
Crie uma classe dentro de “Endpoints” chamada “UserEndpoints” esta classe irá conter os endpoints da api, que seguirão o conceito “Minimal Api” do .NET conforme a listagem abaixo:
public static class UserEndpoints
{
public static void MapUserEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/user").WithTags("User");
group.MapPost("/register", async ([FromServices] UserManager<IdentityUser> userManager, [FromBody] RegisterRequestModel model) =>
{
var user = new IdentityUser { UserName = model.Username, Email = model.Email };
var result = await userManager.CreateAsync(user, model.Password);
return result.Succeeded
? Results.Ok(new { Message = "Usuário registrado com sucesso!" })
: Results.BadRequest(result.Errors);
});
group.MapPost("/login", async ([FromServices] UserManager<IdentityUser> userManager,
[FromServices] IConfiguration config, LoginRequestModel model) =>
{
var user = await userManager.FindByEmailAsync(model.Email);
if (user == null || !await userManager.CheckPasswordAsync(user, model.Password))
return Results.Unauthorized();
// Obtém as roles do usuário
var userRoles = await userManager.GetRolesAsync(user);
// Cria as claims (incluindo roles)
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
// Adiciona as roles como claims
foreach (var role in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"]!));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: config["Jwt:Issuer"],
audience: config["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddHours(1),
signingCredentials: creds);
return Results.Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
});
group.MapGet("/users", async ([FromServices] UserManager<IdentityUser> userManager) =>
{
var users = await userManager.Users.ToListAsync();
List<UserResponseModel> userList = MapUserListResponse(users);
return Results.Ok(userList);
});
group.MapGet("/users/{id}", async ([FromServices] UserManager<IdentityUser> userManager, [FromQuery] string id) =>
{
var user = await userManager.FindByIdAsync(id);
return user != null ? Results.Ok(new UserResponseModel(user)) : Results.NotFound();
});
group.MapPut("/users/{id}", async ([FromServices] UserManager<IdentityUser> userManager,
[FromQuery] string id, [FromBody] UpdateUserRequestModel model) =>
{
var user = await userManager.FindByIdAsync(id);
if (user == null)
return Results.NotFound();
user.Email = model.Email;
user.UserName = model.Username;
var result = await userManager.UpdateAsync(user);
return result.Succeeded ? Results.Ok(user) : Results.BadRequest(result.Errors);
});
group.MapDelete("/users/{id}", async ([FromServices] UserManager<IdentityUser> userManager,
[FromQuery] string id) =>
{
var user = await userManager.FindByIdAsync(id);
if (user == null)
return Results.NotFound();
var result = await userManager.DeleteAsync(user);
return result.Succeeded
? Results.Ok(new { Message = "Usuário deletado com sucesso." })
: Results.BadRequest(result.Errors);
});
}
private static List<UserResponseModel> MapUserListResponse(List<IdentityUser> users)
{
var userList = new List<UserResponseModel>();
foreach (var item in users)
{
userList.Add(new UserResponseModel(item));
}
return userList;
}
}Criada esta classe, veja alguns detalhes dela:
- temos vários endpoints que permitem interagir com a entidade do usuário, desde o seu cadastramento até a sua listagem ou consulta.
Observe a figura abaixo, ela mostra um trecho de código da classe UserEndpoints, no item A, ela define o grupo da rota como “api/user”, desta forma, todos os endpoints declarados internamente terão como seu sufixo “api/user”.
O Item B, cria um endpoint “register” que ficará na rota “api/user/register” o qual será responsável pela inclusão de novos usuários, através do método Post da api, enviando um objeto do tipo “RegisterRequestModel” e devolvendo uma mensagem de sucesso em caso de salvamento bem sucedido ou de erro em caso de não conseguir gravar.
Não há neste momento inicial nenhum tipo de validação, já que o propósito é criar um endpoint mais simples possível para permitir a inclusão de novos usuários.
Para que tudo isso funcione, teremos que incluir mais coisas no nosso program.cs
Passo 1 — Criar usuário
Primeiramente vamos criar um usuário, utilizando a chamada de criação do usuário através da api
fazendo esta chamada criamos o novo usuário que utilizaremos nos testes!
Depois disso, precisamos testar o login !
Passo 2 — Login
Ao realizar o login temos a seguinte resposta:
O que tem dentro do Jwt
Mas daí, o que isso quer dizer? ele diz que foi feita uma autorização do usuário e qual é o e-mail dele, mas não sabemos se qual é o perfil do usuário! Se ele é um admin ou um usuário, e como fazer isso agora?
Bom, para definir o que ele pode ou não acessar, precisaremos utilizar Papéis (Roles) e Declarações (Claims).
Até aqui já temos bastante coisa, aguarde a publicação da parte II onde continuaremos com mais configurações e testes!
Gostou do artigo? clique no ícone👏e também me siga nosso perfil no medium para ver as próximas publicações !! Quer ver mais conteúdos, acesse minhas redes através do Linktree: https://linktree.com/nizzola
