M3 ‐ Seguridad en Web Services - sjperalta/Web-Service-C-sharp GitHub Wiki

Módulo 3: Seguridad en Web Services

Autenticación y Autorización

Métodos de Autenticación
  1. 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.
  2. 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).
  3. 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.

Implementacion de Token-Based Authentication.

image

Para verificar Tokens se puede user el siguiente sitio web. https://jwt.io/

Paso 1: Instalar Paquetes Necesarios

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

Paso 2: Configurar el Middleware de Autenticación en Program.cs

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();

Authorization Popup

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" }
            );
        }
    }
}

Paso 3: Agrega las llaves secretas en tu appSettings.json

 "Jwt": {
    "Issuer": "https://davivienda.com.hn/",
    "Audience": "https://davivienda.com.hn/",
    "Key": "hg9a1ccf8095037f361a4d351e7c0de65f0776bfc2f478ea8d312c763bb6caca"
  }

Paso 4: Crear un Controlador para Autenticación

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; }
    }
}

Paso 5: Proteger Rutas con [Authorize]

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;
        }
    }
}

Paso 6: Probar la Aplicación

Ejecuta la aplicación:

dotnet run
  1. 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.
  2. 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>

Si todo está configurado correctamente, deberías poder acceder a las rutas protegidas con el token JWT.

Resumen

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.

⚠️ **GitHub.com Fallback** ⚠️