Mejores Prácticas: APIs #3

En los posts anteriores, exploramos algunas buenas prácticas que deberíamos implementar. En este tercer artículo, abordaremos adicionalmente temas relacionados con la seguridad y las configuraciones

Seguridad

En primer lugar, es fundamental emplear siempre el canal de comunicación HTTPS. Este protocolo cifra la comunicación de extremo a extremo, dificultando la lectura de los datos en caso de interceptación.

Por otro lado, para reforzar la seguridad de nuestras APIs, la utilización de tokens JWT es altamente recomendada. JWT, como estándar open-source, nos permite transmitir datos entre el cliente y el servidor de manera segura mediante una estructura de objeto JSON. .NET proporciona un soporte integrado que facilita la implementación de esta práctica. A continuación, veremos cómo configurarlo:

builder.Services.AddAuthentication(opt => {
    opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
     options.TokenValidationParameters = new TokenValidationParameters
     {
        //Configuration in here
     };
});

Luego debemos registrarlo para que esté disponible:

app.UseAuthentication();
app.UseAuthorization();

No solamente es posible utilizarlo para autenticación; también podemos implementar autorización mediante Claims de roles que vienen configurados en el JWT.

Ten presente que para esto necesitarás un Identity Provider. Las dos herramientas más conocidas son IdentityServer4, que soporta OAuth2 y OpenID Connect, y también puedes optar por el servicio en la nube de Azure llamado Azure B2C. Puedes utilizarlo de forma gratuita, ya que admite 50 mil inicios de sesión por mes y 50 usuarios registrados sin costo.

Configuración de servicios

A partir de la versión 6 de .net tenemos la configuración de los servicios en nuestra clase program. Veamos un ejemplo de cómo configurar CORS Policies para nuestras APIs.

builder.Services.AddCors(options => 
{ 
    options.AddPolicy("CorsPolicy", builder => 
        builder.AllowAnyOrigin() 
       .AllowAnyMethod() 
       .AllowAnyHeader()); 
});

Este código funciona correctamente y no tendremos inconvenientes. A medida que hemos agregado las configuraciones en nuestra clase Program será cada vez más largo hasta llegar a ser ilegible dependiendo el tamaño de nuestra aplicación. El mejor camino es escribir una clase de extensión estática para reducir la cantidad de líneas de código en nuestro archivo principal y ser más puntual. Recuerda Single responsibility principle.

public static class ServiceExtensions
{
    public static void ConfigureCors(this IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                builder => builder.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
        });
    }
}

Luego podremos volver a nuestro Program (program.cs) e invoquemos la extensión reduciendo el código de esta clase.

builder.Services.ConfigureCors();

Este es solo un ejemplo, pero es muy útil para muchas cosas, como por ejemplo escribir extensiones de servicios o extensión para la inyección de nuestros repositorios.

Configurar los entornos de ejecución

Al crear nuestra aplicación siempre estaremos trabajando en nuestro entorno de desarrollo. Es posible configurar varios entornos de ejecución para nuestra aplicación como un entorno de pruebas o producción. Por esta razón, la mejor de las prácticas, es tener separados nuestros entornos de ejecución.

Para realizar esta configuración, luego de crear nuestro proyecto, veremos que tenemos disponible el archivo appsettings.json. Al lado tiene un símbolo + el cual nos permite extenderlo y ver que ese archivo está conformado por appsettings.Development.json.

Es posible agregar 2 archivos nuevos: uno para test y otro para producción:

Una vez que tengamos diferenciados este archivo y configurados correctamente para cada entorno, dependerá de lo que tengamos configurado en nuestra variable de entorno Environment (ASPNETCORE_ENVIRONMENT) se tomará un archivo u otro.

Eliminar codigo duplicado

Un buen camino para evitar código duplicado es la utilización de ActionsFilters. Estos nos permiten ejecutar código antes o después de la ejecución de una solicitud. Si necesitamos por ejemplo ejecutar código de validación de nuestros modelos no es necesario escribirlos dentro de nuestros actions. 

Seguramente siempre tenemos este código dentro de nuestros métodos:

if (!ModelState.IsValid)
{
  
}

Para evitar este código siempre en nuestro métodos podemos crear un filtro:

public class ModelValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState); 
        }
    }
}

El siguiente paso será registrar nuestro filtro como servicio:

builder.Services.AddScoped<ModelValidationAttribute>();

Con esto último quedará disponible para nuestros métodos Actions.

Routing

Una buena práctica en el enrutamiento es utilizar atributos en lugar de el enrutamiento normal. De esta manera los atributos nos ayudarán a hacer coincidir los nombres de los parámetros con los parámetros reales de los métodos. Por otro lado, lo hará más legible.

[Route("api/[controller]")]
public class CustomerController: Controller
{
     [Route("{id}")]
     [HttpGet]
     public IActionResult GetCustomerById(Guid id)
     {
            
     }  
}

o podemos usar un camino más corto:

[Route("api/customers")]
public class CustomersController: Controller
{
     [HttpGet("{id}")]
     public IActionResult GetCustomerById(Guid id)
     {
            
     }  
}

Recuerdo que siempre debes respetar la convención de nomenclaturas predeterminada para las rutas: nombres descriptivos para las acciones, pero para los endpoints sustantivos y verbos. Veamos un ejemplo:

[Route("api/customers")]
public class CustomersController : Controller
{ 
    [HttpGet]
    public IActionResult GetAllCustomers()
    { 
    }
    [HttpGet("{id}"]
    public IActionResult GetCustomerById(Guid id)
    {          
    }
}

Cambiar el tipo de contenido retornado

El uso más común hoy es el retornar objetos json desde nuestras API. Pero en muchos casos me he encontrado con la necesidad de retornar un XML debido a que el cliente consumía este formato porque utilizaba WCF.

Debemos crear una configuración para que los servicios para formatear la respuesta según sea necesario. Primero, aceptaremos la negociación.

builder.Services.AddMvc(config => 
{ 
    // Add XML Content Negotiation 
    config.RespectBrowserAcceptHeader = true; 
});

Usar Async

Una buena práctica es utilizar métodos asincrónicos para evitar posibles cuellos de botella en el rendimiento de los servicios y mejoraremos la capacidad de respuesta de nuestros servicios APIs. Veremos realmente mejoras cuando debemos salir de nuestra aplicación como consumir datos desde una base de datos o un servicio de terceros.

veamos un ejemplo sincronico:

Ahora veámoslo de forma asincrónica.

[HttpGet] 
public async Task<IActionResult> Get() 
{ 
    var customers = await _repository.Customers.GetAllCustomerAsync(); 
    _logger.LogInfo($"Returned all customers from database.");
    return Ok(customers); 
}

Ten en cuenta que este es un ejemplo básico. Si quieres que indaguemos más en detalle dejame en los comentarios y lo haremos.

Conclusiones

Este marca la conclusión del conjunto de posts sobre las mejores prácticas para nuestras Web APIs. Espero que hayas disfrutado del contenido y que te resulte útil en tu día a día de desarrollo. Si tienes alguna otra práctica que consideres importante y que desees compartir, por favor, déjame tu comentario y con gusto lo tomaré en cuenta. ¡Gracias!

0 0 votos
Valora la Publicación
Suscribirse
Notificación de
guest
0 Comentarios
Feedback en línea
Ver todos los Comentarios

Comentarios Recientes

0
Nos encantaría conocer tu opinión: ¡comenta!x
Ir a la barra de herramientas