En este post vamos a ver cómo podemos incluir seguridad a nuestros servicios que se comunican máquina a máquina, ya que cuando tenemos este escenario no utilizamos JWT u otras formas de autenticación.
Índice
1 - Qué es una API Key?
Una API Key es un identificador único que sirve para identificar y autenticar a una aplicación o usuario.
Como su nombre indica, esta key se utiliza para permitir el acceso a una API. y la Api Key se limita a dar acceso a la aplicación o usuario, pero a diferencia de los JWT la API key no expira y utilizamos siempre la misma. Por este motivo, tanto el que realiza la llamada como el que recibe dicha llamada tienen conocimiento de la key en cuestión.
A pesar de que he dicho que la api key puede identificar quién hace la llamada, la realidad es que ese “quién” en verdad es “qué sistema” ya que la api key se utiliza en el 99.9% de casos para hacer llamadas entre sistemas, ya bien sea de una api externa o interna, suelen ser utilizadas cuando la acción sucede de forma automática.
Además, las keys suelen ser 1 a 1, osea tienes una app, tienes una key y esa key te permite identificarte en la aplicación a la que estás consultando.
Personalmente pienso que la API key no es suficiente para identificar y dar permisos, para mí una API key te debería dar acceso a todo (identificar), y luego limitar el uso pero una api key no te da el mismo control que te da un token a la hora por ejemplo de permitir acceso a X o Y. Obviamente, la aplicación dueña de esa API puede almacenar esa información en una base de datos, pero para hacer eso, utiliza JWT o un API Token.
Por ejemplo la API de youtube sigue este mecanismo, te da una key para tu aplicación en concreto, no tienes un usuario y contraseña como tal.
1.1 - Que pasa con una API key comprometida?
hay que cambiarla, esa api identifica a tu maquina, por lo tanto la máquina a la que realizas las llamadas no va a tener ni idea de si es tu maquina de verdad o de si es un tercero intentando sacar información.
Esto sobre todo pasa en apis públicas, que puedes llamar desde cualquier parte, en la empresa privada lo más normal es que tengan limitado el acceso desde cierta IP, por ejemplo empresa A está ubicada en la ip 25
y la empresa B creadora de la API tiene configurado que solo desde la ip25
puede recibir llamadas.
Por norma general, las empresas lo hacen así, obviamente por seguridad, de todas formas, si la key ha sido comprometida, deberías generar una.
2 - Cuándo utilizar una API key
Como he explicado hace un momento se hace para controlar quien y cuanto utilizan la API, para comprobar que nadie hace un uso abusivo de nuestra API ni nadie intenta hacer nada malicioso.
Además nos permite bloquear el tráfico anónimo.
3 - Cuando No utilizar una API key
Debemos evitar su uso para identificar al usuario en cuestión que realiza la llamada, para eso tenemos los JWT mencionados anteriormente.
Además recordamos que la API key es por aplicación no usuario.
4 - Implementación de una API key en .NET
Ahora vamos a ver un ejemplo sencillo de cómo implementar una API Key dentro de un entorno de microservicios con .NET
Para este ejemplo, voy a reducir el ejemplo a lo absurdo, un solo cliente con un solo token, por lo que ni lo almacenamos en memoria ni nada, simplemente lo tenemos configurado en nuestro appsettings.json
. así simplificamos el asunto.
Este código está dentro del proyecto de Distribt el cual puedes encontrar en GitHub al cual le vamos a añadir dentro de la arquitectura el uso de API Keys, dentro de nuestra API a la que tiene acceso el cliente final.
Aquí podemos ver una imagen de la arquitectura completa del sistema, pero en este post nos vamos a centrar únicamente en el cuadradito verde.
Si no estas familiarizado con los sistemas distribuidos te recomiendo que sigas el siguiente curso:
Lo que la API hace es a través de YARP (proxy inverso) enruta la llamada de ese microservicio al interno, de esta forma, el cliente únicamente tiene acceso a dicho microservicio.
en nuestro caso vamos a probar con el endpoint de los health checks https://localhost:7022/reports/health
Bueno vamos al lío, lo que tenemos que hacer es en nuestro appsettings
crear el valor de la API Key, en un entorno de producción esta API Key la generarías de forma semi-automática para cada cliente, caducando las anteriores, etc.
{
...
"ApiKey": {
"clientId": "1",
"value": "b92b0bdf-da95-42a8-a2b1-780ca461aaf3"
}
}
Con esto únicamente tenemos que leer el valor, ya bien sea utilizando options pattern o directamente de IConfiguration, y compararlo con el que nos llega del usuario.
Para ello cogemos la información:
public class ApiKeyConfiguration
{
public string? ClientId { get; init; }
public string? Value { get; init; }
}
// en la clase startup.cs
services.Configure<ApiTokenConfiguration>(configuration.GetSection("ApiKey"));
Y el siguiente paso es crear un middleware
o un FilterAttribute
que compruebe si ese token viene en la request.
En nuestro caso vamos a crear un middleware
. Pero la lógica para un filterAttribute
es la misma, únicamente que tendrás que habilitarlo donde quieras utilizarlo, mientras que en un middleware irá en todos endpoints por defecto.
como nota decir que la API key vendrá como parte de los headers en concreto como parte del HTTP Authorization header (spec), pero opcionalmente puedes ponerlo como parte de la query string, ya que muchos servicios lo hacen así.
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
public ApiKeyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IOptions<ApiKeyConfiguration> apiToken)
{
if (context.Request.Headers.TryGetValue("apiKey", out StringValues apiKey))
{
if (apiKey == apiToken.Value.Value)
await _next(context);
else
ReturnApKeyNotfound();
}
else
{
ReturnApKeyNotfound();
}
void ReturnApKeyNotfound()
{
throw new UnauthorizedAccessException("The API Key is missing");
}
}
}
Y ahora solo tenemos que añadir el middleware; una cosa a tener en cuenta es que el middleware no se va a ejecutar cuando llamemos al endpoint health
o health-ui
del microservicio, pero si lo hará cuando llamemos al del microservicio que hace referencia:
//Do not act on /health or /health-ui
webApp.UseWhen(context => !context.Request.Path.StartsWithSegments("/health"),
appBuilder => appBuilder.UseMiddleware<ApiKeyMiddleware>()
);
Y ahora solo nos queda probarlo.
Si desde postman hacemos la request con un api key erróneo, o sin api key vemos que nos devuelve un error:
Pero cuando enviamos el valor correcto, nos muestra el resultado esperado:
Conclusión
En este post hemos visto qué es una API key
Cuándo implementar una API Key
y cómo implementar una API Key en .net