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

mgpaixao

Matheus Paixão

Posted on March 2, 2022

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
Matheus Paixão

Posted on March 2, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Aprenda Lógica de Programação
csharp Aprenda Lógica de Programação

November 20, 2024

c# advanced : Positional Patterns
csharp c# advanced : Positional Patterns

November 2, 2024

Nulable Types
devto Nulable Types

October 18, 2024

String operations
spring String operations

October 14, 2024