DEV Community

Cover image for C# - Criando API Login com Jwt Token - Autorização e Autenticação - Módulo 2
Matheus Paixão
Matheus Paixão

Posted on

C# - Criando API Login com Jwt Token - Autorização e Autenticação - Módulo 2

Sejam bem vindos novamente marujos, bora continuar remando rumo à mais um módulo desse projeto incrível.

 
Código Módulo 2 -> Repositório GitHub

 

Remar

 
Nesse módulo irei abordar a evolução do projeto da API de Login com autorização através do JWT Token. Modulo 1

 

Migrando .Net 5 para .Net 6

 

1 - No arquivo .csproj altere o TargertFramework para net6.0 e dentro do mesmo <PropertyGroup> adicione <Nullable>enable</Nullable> e <ImplicitUsings>enable</ImplicitUsings> e o resultado deve ficar assim:

  <PropertyGroup>     
      <TargetFramework>net6.0</TargetFramework>
      <Nullable>enable</Nullable>
      <ImplicitUsings>enable</ImplicitUsings>     
  </PropertyGroup>
Enter fullscreen mode Exit fullscreen mode

2 - Atualize os pacotes Nugets

3 - Migrando dados da Startup.cs para Program.cs

No .net 6 você não precisa mais dos métodos ConfigureServices() e do Configure(), você precisa apenas estanciar o WebApplication Builder na classe program para poder ter acesso aos métodos de configuração.
 
Então no nosso programa o método ConfigureServices() ficou assim:
 
IMPORTANTE: Mudei o JWTkey para o nosso secrets, para não deixar exposto a nossa chave de criptografia.
 

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<TokenService>();
builder.Services.AddDbContext<DataContext>();
builder.Services.AddControllers();


//JWTConfig 
var key = Encoding.ASCII.GetBytes(builder.Configuration["Key:JwtKey"].ToString());
builder.Services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
    x.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        ValidateAudience = false,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false
    };
});

//Configuração do Swagger para adicionar o Bearer Token na auth
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "JWTAuthAuthentication2", Version = "v1" });
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
    {
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Description = "JWT Authorization header using the Bearer scheme",
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                          new OpenApiSecurityScheme
                          {
                              Reference = new OpenApiReference
                              {
                                  Type = ReferenceType.SecurityScheme,
                                  Id = "Bearer"
                              }
                          },
                         new string[] {}
                    }
                });
});
Enter fullscreen mode Exit fullscreen mode

E o método Configure() vai ficar assim:

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JWTAuthAuthentication2 v1"));
}
app.UseHttpsRedirection();
app.UseRouting();

////JWTConfig - Adicionar sempre Authentication antes de Authorization
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
app.Run();
Enter fullscreen mode Exit fullscreen mode

Repare que não fiz grandes alterações, só passei as configurações da startup para a classe Program.cs e agora você pode excluir a classe Startup.cs adotoando os padrões do .net 6

Crud de Roles

 

Create Read Update Delete - CRUD

 
Crud
Como vamos fazer um crud completo da entidade Roles, é de boa prática criar uma Controller só pra isso, separando as responsabilidades.

Nessa Controller só terá autorização quem tiver acesso de Admin.
Regas de negócio que aplicamos:

  • 1 - Usuário não pode alterar sua própria role
  • 2 - Precisa ter ao menos 1 usuário admin no banco de dados
  • 3 - Não pode haver duas Roles com o mesmo nome

E assim ficou a RolesController

Post

    [HttpPost("AddRole")]
    public async Task<IActionResult> AddRole(RoleViewModel roleRequest,
                                             [FromServices] DataContext context)
    {
        if (!ModelState.IsValid) return BadRequest(ModelState);
        if (context.Roles.Any(x => x.Name.Equals(roleRequest.Name))) return BadRequest("Role duplicated");

        var roleModel = new Role
        {
            Name = roleRequest.Name,
        };

        await context.Roles.AddAsync(roleModel);
        await context.SaveChangesAsync();
        return Ok(roleModel);
    }
Enter fullscreen mode Exit fullscreen mode

Patch Change User Role

    [HttpPatch("ChangeUserRole")]
    public async Task<IActionResult> ChangeUserRole(int UserId,
                                                    int NewRoleId,
                                                    [FromServices] DataContext context)
    {
        if(!ModelState.IsValid) return BadRequest(ModelState);

        //Consultas
        var userModel = await context
            .Users
            .FirstOrDefaultAsync(x => x.Id == UserId);

        var roleModel = await context
            .Roles
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == NewRoleId);

        //Validações
        if (userModel == null || roleModel == null)
            return StatusCode(401, "Invalid Id");

        //Verificando quantos usuarios admins temos no sistema
        var userList = await context.Users.ToListAsync();
        int adminUsers = 0;
        foreach (var item in userList)
        {
            if (item.RolesId == 2) adminUsers++;
        }

        //Verificando se sobrará pelo menos 1 admin no sistema
        if (userModel.RolesId == 2 && adminUsers <= 1) return StatusCode(400, "You must have at least 1 admin user in the database");

        userModel.Roles = roleModel;

        //Admin não pode mudar sua própria role
        if (User.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value.Equals(userModel.Id.ToString())) return StatusCode(400, "You cannot change your own role");


        await context.SaveChangesAsync();
        return Ok(userModel.Name + " Role changed to " + roleModel.Name);
    }
Enter fullscreen mode Exit fullscreen mode

Get Mostrar todos os Roles

[HttpGet("GetRoles")]
    public async Task<IEnumerable<Role>> GetRoles([FromServices] DataContext context)
    {
        return await context.Roles.ToListAsync();
    }
Enter fullscreen mode Exit fullscreen mode

Patch Edit Role

    [HttpPatch("EditRole")]
    public async Task<IActionResult> EditRole([FromServices] DataContext context,
                                                              Role roleRequest)
    {
        if(!ModelState.IsValid) return BadRequest(ModelState);

        var roleModel = await context.Roles.AsNoTracking().FirstOrDefaultAsync(x => x.Id == roleRequest.Id);

        //Validações
        if (roleModel == null) return NotFound("Role not found");
        if (context.Roles.Any(x => x.Name.Equals(roleRequest.Name))) return BadRequest("Role duplicated");


        context.Roles.Update(roleRequest);
        await context.SaveChangesAsync(); 
        return Ok(roleRequest);        
    }
Enter fullscreen mode Exit fullscreen mode

Delete Roles

[HttpDelete("DeleteRoles")]
    public async Task<IActionResult> DeleteRoles(int roleId,
                                                [FromServices] DataContext context)
    {
        if(!ModelState.IsValid) return BadRequest(ModelState);

        var roleModel = await context.Roles.FirstOrDefaultAsync(x => x.Id == roleId);

        //Validações
        if (roleModel == null) return NotFound("Role not found");
        if (roleModel.Name.Contains("admin")) return BadRequest("Cannot delete admin role");

        context.Roles.Remove(roleModel);
        await context.SaveChangesAsync();
        return Ok("Role " + roleModel.Name + " Deleted");
    }
Enter fullscreen mode Exit fullscreen mode

O código está inteiro comentado e separei os blocos por #region para facilitar leitura e busca dos métodos.
 
Qualquer dúvida, é só comentar ou entrar em contato comigo.
 

No proximo módulo eu adicionarei:

  • CRUD de Usuários
  • introdução do Repository Pattern
  • Divisão em 3 Projetos (Infra - Domain - API)   Obrigado por lerem mais um módulo da série dessa API.
mgpaixao image

Top comments (0)