C# - Criando API Login com Jwt Token - Autorização e Autenticação - Módulo 2
Matheus Paixão
Posted on March 2, 2022
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
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>
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[] {}
}
});
});
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();
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
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);
}
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);
}
Get Mostrar todos os Roles
[HttpGet("GetRoles")]
public async Task<IEnumerable<Role>> GetRoles([FromServices] DataContext context)
{
return await context.Roles.ToListAsync();
}
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);
}
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");
}
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.
Posted on March 2, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.