M3 ‐ Seguridad en Web Services - sjperalta/Web-Service-C-sharp GitHub Wiki
-
Autenticación Básica (Basic Authentication):
- Descripción: Transmite las credenciales de usuario (nombre de usuario y contraseña) codificadas en Base64 en el encabezado HTTP.
- Uso: Fácil de implementar pero menos segura sin HTTPS, ya que las credenciales pueden ser interceptadas.
-
Token-Based Authentication:
- Descripción: Después de que un usuario se autentica, se genera un token (como JWT - JSON Web Token) que se envía en cada solicitud subsecuente en el encabezado HTTP.
- Uso: Más seguro y escalable que la autenticación básica, ideal para aplicaciones móviles y SPA (Single Page Applications).
-
OAuth:
- Descripción: Un protocolo abierto que permite la autorización segura de terceros para acceder a recursos protegidos sin compartir las credenciales del usuario.
- Uso: Ampliamente utilizado por grandes plataformas (como Google y Facebook) para delegar acceso a recursos.
Nota:
A partir de .NET 6, el modelo de inicio para aplicaciones ASP.NET Core cambió a un enfoque más simplificado, utilizando solo el archivo Program.cs
en lugar de Startup.cs
. Este nuevo modelo de inicio combina la configuración del host y la aplicación en un único lugar, haciendo el código más conciso y fácil de entender.
Para verificar Tokens se puede user el siguiente sitio web. https://jwt.io/
Instala los paquetes necesarios para la autenticación JWT:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.IdentityModel.Tokens
dotnet add package Microsoft.IdentityModel.JsonWebTokens
Configura el middleware de autenticación JWT en el archivo Program.cs
:
Referencias:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-8.0
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using TodoApiRestfull.Data;
var builder = WebApplication.CreateBuilder(args);
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"] ?? "");
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
LogValidationExceptions = false,
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.NoResult();
context.Response.StatusCode = 401;
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync(context.Exception.ToString());
}
};
});
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
// Configurar Swagger
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JwtAuthApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseSwagger();
app.UseSwaggerUI();
// Seed the database with the default user
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<TodoContext>();
context.Database.EnsureCreated();
}
app.Run();
Para permitir que swagger aplique los headers de autorization, es necesario aplicar la siguiente configuracion :
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JwtAuthApi", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
});
Database Seeds, se utilizan para forzar una insercion/actualizacion de datos antes de cualquier otra operacion en la base de datos.
// Seed the database with the default user
using (var scope = app.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<TodoContext>();
context.Database.EnsureCreated();
}
Para generar datos de usuarios en la base de datos vamos a utilizar un funcion especial del contexto de la base de datos TodoContext.cs
la funcion OnModelCreating
permite insercion/actualizacion de registros antes antes que se pueda efectuar cualquier operacion en la db.
crear la clase user en la carpeta Models
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TodoApiRestfull.Models
{
public class User
{
public long Id { get; set; }
public string Username { get; set; } = "";
public string Password { get; set; } = "";
}
}
Modificar el contexto del proyecto.
using Microsoft.EntityFrameworkCore;
using TodoApiRestfull.Models;
namespace TodoApiRestfull.Data
{
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public TodoContext(){}
public virtual DbSet<TodoItem> TodoItems { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Seed data
modelBuilder.Entity<User>().HasData(
new User { Id = 1, Username = "admin", Password = "admin123" }
);
}
}
}
"Jwt": {
"Issuer": "https://davivienda.com.hn/",
"Audience": "https://davivienda.com.hn/",
"Key": "hg9a1ccf8095037f361a4d351e7c0de65f0776bfc2f478ea8d312c763bb6caca"
}
Crea un controlador para manejar la autenticación y generación de tokens. Agrega un archivo AuthController.cs
en la carpeta Controllers
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using TodoApiRestfull.Data;
namespace TodoApiRestfull.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly TodoContext _context;
private readonly IConfiguration _configuration;
public AuthController(TodoContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
[HttpPost("login")]
public async Task<ActionResult> Login([FromBody] UserLogin userLogin)
{
var userExist = await _context.Users
.AnyAsync(user => user.Username == userLogin.Username && user.Password == userLogin.Password);
if (userExist)
{
var token = GenerateJwtToken(userLogin.Username);
return Ok($"Bearer {token}");
}
return Unauthorized();
}
private string GenerateJwtToken(string username)
{
var issuer = _configuration["Jwt:Issuer"];
var audience = _configuration["Jwt:Audience"];
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"] ?? "");
var signingCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha512Signature
);
var subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(JwtRegisteredClaimNames.Email, username),
});
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = subject,
Issuer = issuer,
Audience = audience,
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = signingCredentials
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtToken = tokenHandler.WriteToken(token);
return jwtToken;
}
}
public class UserLogin
{
public string Username { get; set; }
public string Password { get; set; }
}
}
Protege tus controladores o acciones específicas usando el atributo [Authorize]
. Por ejemplo, crea un controlador TodoItemsController.cs
para manejar los TodoItem
:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace JwtAuthApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
{
var result = await _context.TodoItems.ToListAsync();
return result;
}
}
}
Ejecuta la aplicación:
dotnet run
-
Generar Token:
- Usa una herramienta como Postman para enviar una solicitud POST a
https://localhost:5001/api/Auth/login
con el cuerpo:{ "username": "admin", "password": "admin123" }
- Esto devolverá un token JWT.
- Usa una herramienta como Postman para enviar una solicitud POST a
-
Acceder a Rutas Protegidas:
- Usa el token recibido en el encabezado de autorización para acceder a
https://localhost:5001/api/TodoItems
:GET /api/TodoItems HTTP/1.1 Host: localhost:5001 Authorization: Bearer <your_jwt_token_here>
- Usa el token recibido en el encabezado de autorización para acceder a
Si todo está configurado correctamente, deberías poder acceder a las rutas protegidas con el token JWT.
Este ejemplo cubre la implementación básica de autenticación JWT en un proyecto de .NET Core 6+, utilizando solo el archivo Program.cs
. Este enfoque simplificado es parte del nuevo modelo de inicio de .NET 6 y posterior, y combina la configuración del host y la aplicación en un solo archivo, haciendo el proceso más directo y fácil de gestionar.