Seguridad en los servicios REST con .NET Core

abigail armijo
Dec 20, 2018 · 10 min read

Una forma común de manejar la seguridad en tus servicios, es mediante la seguridad basada en tokens. En el cual después de que el usuario inicia sesión el servidor regresa una cadena codificada con los datos que identifican al usuario(claims), este token se debe enviar en cada petición a los servicios rest, el servidor valida el token si es correcto obtiene los datos del usuario y si el usuario tiene el permiso o el rol correcto muestra la información, una explicación de forma gráfica es la siguiente:

Image for post
Image for post
Funcionamiento de Json Web Token
  1. El usuario inicia sesión ya sea en una aplicación móvil o en navegador. Internamente se envía una petición POST con el usuario y contraseña del usuario.
  2. El servidor valida el usuario y contraseña enviados y genera un token, el cual es básicamente una cadena codificada donde agrega información como el Id del usuario, los roles que tiene el usuario, y el tiempo en el cual es token es válido por ejemplo 1 hora, 2 horas, 1 día, una vez caducado el token el usuario debe volver a iniciar sesión o pedir una renovación del token.
  3. El navegador o la aplicación recibe el token y lo guarda. Se puede guardar el token en el local storage,cookies seguras, de la página si es una aplicación web o en los datos tu aplicación móvil. Por motivos de seguridad si guardas el token en el local storage debes agregar otra validación como por ejemplo la ip de la cual el usuario realizó el login, para que si un hacker obtiene el token e intenta acceder desde otra ciudad o país notificar al usuario para cancelar el token.
  4. El usuario consulta alguna información del sistema, como por ejemplo la lista de clientes. En el servicio GET con la petición para la lista de clientes, en el header se envia el token que el servidor regreso en el paso 2.
  5. El servidor válida el token si es válido y el usuario tiene permiso para consultar la información regresa la información, si no regresa un código de error (401) No autorizado.

Un Json Web Token es una cadena codificada en base64 formada por 3 partes las cuales están separadas por un punto.

  1. Header: Indica el algoritmo y tipo de token
  2. Payload: Datos del usuario, caducidad del token, roles del usuario
  3. Signature: Incluye una llave secreta para validar el token

Para poder generar los tokens necesitamos:

  • LLave secreta: Es una llave que permite encriptar/desencriptar la información del token
  • Issuer: Es quien genera el token, por lo general es la URL del servidor que contiene los servicios

La información adicional que guardas en el token como el id del usuario se conoce como Claims, la lista de claims disponibles es:

En .NET Core implementar este tipo de seguridad es relativamente fácil.

Seguridad basada en roles

Una forma fácil de manejar la seguridad es con roles, por ejemplo para un sistema de ventas se pueden tener los siguientes grupos (roles) de usuarios:

  • Administradores: Pueden consultar y modificar cualquier información en el sistema.
  • Vendedores: Se encargan de revisar los pedidos de los clientes y enviar los productos.
  • Clientes: Solamente pueden comprar productos

De esta manera cuando un usuario inicia sesión aparte de regresar el id del usuario, regresas la lista de roles que tiene el usuario, asi en cada petición el servidor puede validar el rol que tiene el usuario en el token y si tiene el rol adecuado muestra la información.

  1. Agregamos el siguiente código en nuestro archivo Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//Codigo
services.AddAuthentication(JwtBearerDefaults
.AuthenticationScheme)
.AddJwtBearer(cfg => {
cfg.Audience = Configuration["Tokens:Issuer"];
cfg.Authority = Configuration["Tokens:Issuer"];
cfg.TokenValidationParameters = new
TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(
Configuration["Tokens:Key"])
);
});
}

3. Agregamos lo siguiente en nuestro archivo appsettings.json, de esta manera podemos cambiar nuestra llave secreta sin tener que volver a generar y copiar nuestra dll en el servidor.

{
"Tokens": {
"Key": "tullavesecreta",
"Issuer": "http://www.midominio.com/"
},

En nuestro método login agregamos el código para generar y regresar el token, también le indicamos que este servicio permite el acceso anónimo, es decir que permite que este accesible sin realizar ninguna validación de seguridad

[HttpPost("Login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody]LoginDTO loginDTO)
{
JwtSecurityToken token;
DateTime expiration;
//Tu validación de usuario password
var llave = Encoding.UTF8.GetBytes(Config["Tokens:Key"])
var key = new SymmetricSecurityKey(llave);
var creds = new SigningCredentials(key,
SecurityAlgorithms.HmacSha256);
token = new JwtSecurityToken(Config["Tokens:Issuer"],
Config["Tokens:Issuer"],
claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: creds);
var claims = new Claim[]
{
new Claim(ClaimTypes.Sid, usuario.Id.ToString()),
new Claim(ClaimTypes.Role, usuario.Rol),
new Claim("Empresa",usuario.Empresa.ToString())
};
tokenHandler = new JwtSecurityTokenHandler().WriteToken(token);
expiration = token.ValidTo;
}

En los métodos de los servicios donde vamos a restringir la información agregamos:

[HttpGet]
[Authorize(Roles = "Administrador, Ventas")]
public List<Categoria> GetCategoria()
{
return categoriaDAO.ObtenerTodo();
}

Seguridad basada en directivas

Puedes definir la seguridad de un servicio si cumple con un claim, por ejemplo el servicio esta disponible solo para los usuarios de la empresa 1 y 2.

  1. En nuestro archivo startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options => {
options.AddPolicy("Empresa", policy =>
policy.RequireClaim("Empresa", "1", "2"));});

2. En nuestra clase controller del método que deseamos restringir

public class ConfiguracionController : Controller 
{
[Authorize(Policy = "Empresa")]
public List<Configuracion> GetConfiguracion()
{
}
}

Si deseamos que todos los métodos de un servicio solamente estén disponibles para personas mayores de 18 años:

  1. En nuestro archivo startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("MayorEdad", policy =>
policy.Requirements.Add(new RequisitoEdadMinima(18)));
});
}

2. Crea una nueva clase RequisitoEdadMinima.cs que implemente las interfaces AuthorizationHandler y IAuthorizationRequirement.

Agrega tu código con tu validación personalizada. Para este ejemplo necesitamos obtener la fecha de nacimiento del usuario el cual previamente en el login se debe haber agregado como claim.

La forma de obtener un claim en código es

context.User.FindFirst(c => c.Type==ClaimTypes.DateOfBirth).Value);

Para regresar un mensaje de error y que se ejecute el servicio regresamos context.Succeed(requirement) en caso de que no tenga permiso regresamos Task.CompletedTask

public class RequisitoEdadMinima :  
AuthorizationHandler<MinimumAgeRequirement>,
IAuthorizationRequirement
{
int _edadMinima;
public RequisitoEdadMinima(int edadMinima)
{
_edadMinima = edadMinima;
}

3. Si agregas la restricción en la clase, aplica para todos los servicios del controlador.

[Authorize(Policy = "MayorEdad")] 
public class AlcoholController : Controller
{
//Codigo de los métodos GET, POST, PUT, DELETE
}

Seguridad Personalizada

Puedes crear tu propia seguridad, por ejemplo para validar si un usuario tiene el permiso de agregar registros. Deseas que la validación se realice al momento en que el usuario quiere consultar o modificar la información al sistema.

  1. Creamos una clase Operaciones.cs para definir las operaciones que vamos a realizar
public static class Operaciones
{
public static OperationAuthorizationRequirement Crear = new
OperationAuthorizationRequirement { Name = "Crear" };
public static OperationAuthorizationRequirement Consultar = new
OperationAuthorizationRequirement { Name = "Consultar" };
public static OperationAuthorizationRequirement Modificar = new
OperationAuthorizationRequirement { Name = "Modificar" };
public static OperationAuthorizationRequirement Borrar = new
OperationAuthorizationRequirement { Name = "Borrar" };
}

2. Creamos una clase PermisoDTO el cual contiene el nombre de la opción del sistema que queremos validar

public class PermisoDTO
{
public string Opcion { get; set; }
}

3. Agregamos una nueva clase PermisoEditHandler que hereda de la clase AuthorizarionHandler la cual recibe como parámetros genéricos un objeto de la clase OperationAuthorizationRequirement y le podemos pasar como parámetro adicional cualquier otro objeto con los datos que necesitamos para validar el permiso, en este caso agregamos nuesta clase PermisoDTO

public class PermisoEditHandler : 
AuthorizationHandler<OperationAuthorizationRequirement, PermisoDTO>
{
private BaseDatosContext contexto;
public PermisoEditHandler(IConfiguration config)
{
contexto = new BaseDatosContext(config);
}

protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement operacion,
PermisoDTO permiso)
{
//Si no existe el claim del usuario se regresa un error
if (!context.User.HasClaim(c => c.Type == ClaimTypes.Sid))
{
context.Fail();
}
var usuarioId = Convert.ToInt32(context.User.FindFirst(c =>
c.Type == ClaimTypes.Sid).Value);
var permisoXUsuario = new PermisoXUsuario(contexto);
if (!permisoXUsuario.TienePermisoUsuario(
usuarioId, permiso.Opcion, operacion.Name)

3. En nuestra clase startup.cs agremos nuestra clase personalizada.

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorizationHandler,
PermisoEditHandler>();

4. En el servicio que deseamos realizar esta validación

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]Empresa
empresa)
{
var authorizationResult = await
_authorizationService.AuthorizeAsync(User, permiso,
Operaciones.Crear);
if (authorizationResult.Succeeded)
{
//Tu codigo
}
else
{
return Forbid( "El usuario no tiene permiso para agregar,
verifique por favor... ")
}
}

Seguridad con filtros

Otra forma de realizar este tipo de validación mas personalizada es mediante una característica llamada Filtros, el cual se ejecuta antes de la llamada a cada servicio para validar que tenga permiso o al final de cada servicio para por ejemplo guardar los datos del usuario que realiza modificaciones al registro, por ejemplo cuando y a que hora modificó el registro, cual registro modificó.

  1. Creamos una clase Operaciones.cs para definir las operaciones que vamos a realizar
public static class Operaciones
{
public static OperationAuthorizationRequirement Crear = new
OperationAuthorizationRequirement { Name = "Crear" };
public static OperationAuthorizationRequirement Consultar = new
OperationAuthorizationRequirement { Name = "Consultar" };
public static OperationAuthorizationRequirement Modificar = new
OperationAuthorizationRequirement { Name = "Modificar" };
public static OperationAuthorizationRequirement Borrar = new
OperationAuthorizationRequirement { Name = "Borrar" };
}

2. Creamos una clase PermisoDTO el cual contiene el nombre de la opción del sistema que queremos validar

public class PermisoDTO
{
public string Opcion { get; set; }
}

3. Creamos una clase Controller que será una clase de la cual todos nuestros contollers heredan. Esta clase tendrá una propiedad que nos indica nombre de la información que deseamos validar. Por ejemplo Ventas

public class BaseController : Controller
{
public PermisoDTO permiso;
}

4. Creamos una nueva clase llamada PermisoFilter que hereda de IActionFilter

public class PermisoFilter : IActionFilter
{
//Este código se manda llamar antes de cada servicio
public void OnActionExecuting(ActionExecutingContext context)
{
var controller = context.Controller as BaseController;
//Obtenemos el método del servicio que se va a ejecutar
string accion = context.RouteData.Values["action"]
.ToString();
string operacion = string.Empty;
switch (accion)
{
case "Post":
operacion = Operaciones.Crear.Name;
break;
case "Put":
operacion = Operaciones.Modificar.Name;
break;
case "Delete":
operacion = Operaciones.Borrar.Name;
break;
case "Get":
operacion = Operaciones.Consultar.Name;
break;
}
//Validamos que el token tenga el claim sid
if (!Usuario.HasClaim(c => c.Type == ClaimTypes.Sid))
{
return context.Result = new JsonResult(new
{
StatusCode = 403,
Value = "Token incorrecto",
ContentType = customError.ContentType
};
}
//Esta clase contiene la lógica para validar que el usuario
//tiene permiso para la opción
UsuarioDAO usuario = new UsuarioDAO();
if (!usuario.TienePermiso(usuarioId,
controller.permiso.Opcion,
operacion)
return context.Result = new JsonResult(new
{
StatusCode = 403,
Value = "El usuario no tiene permiso",
ContentType = customError.ContentType
};
}

3. Agregamos el filtro a nuestra clase controller y en el constructor asignamos el nombre de la opción que deseamos validar

[TypeFilter(typeof(PermisoFilter))]
public class ProductoController : BaseController
{
public ProductoController()
{
permiso.Opcion = "productos";
}
}

Puedes ver la documentación oficial de microsoft aquí


Como agregar SSL a tus servicios en un servidor Linux de forma gratuita

También se recomienda que el acceso a tus servicios sea mediante SSL, el cual es un protocolo de seguridad para proteger la comunicación entre los clientes de tu aplicación (sistema web, aplicación móvil) y tu servidor.

En este ejemplo voy a utilizar certificados gratuitos de Let’s Encrypt y la herramienta Certbot para linux, puedes realizar los pasos que se encuentran en la página de Certbot si deseas agregar SSL a los sitios web de tus servidores apache o nginx. Estos certificados son válidos por 30 días y los puedes renovar en automático con un comando de certbot.

Estos pasos son para Ubuntu, puedes consultar la página de certbot para ver los pasos para las otras distribuciones de linux

1. Agregamos el repositorio de certbot

sudo add-apt-repository ppa:certbot/certbot

2. Actualizamos la lista de paquetes

sudo apt-get update

3. Instalamos el paquete de Certbot

sudo apt-get install certbot

4. Creamos el certificado para nuestro dominio, el servidor nginx no debe estar corriendo al momento de crear el certificado.

sudo certbot certonly --standalone --preferred-challenges http -d example.com

Nos crea los siguientes archivos

cert.pem  chain.pem  fullchain.pem  privkey.pem  README

En mi caso utilizare nginx el cual ya debe estar instalado en ubuntu de lo contrario lo instalamos, puedes ver la siguiente página donde explica como instalar nginx

También puedes consultar la documentación de microsoft para configurar nginx para correr tu aplicación de .net en linux

Agregamos el siguiente archivo /etc/nginx/proxy.conf

proxy_redirect          off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;

Modificamos el archivo de configuración de nginx /etc/nginx/nginx.conf para indicarle que tome nuestro archivo proxy.conf

http {
include /etc/nginx/proxy.conf;
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
server_tokens off;

Si al probar tus servicios te marca un error 503, puedes cambiar el limíte de la siguiente manera, con esta instrucción limitamos el número de peticiones (request) por segundo de acuerdo por ejemplo a la ip, con esto prevenimos ataques de DDOS o ataques de fuerza bruta.

limit_req_zone $binary_remote_addr zone=one:20m rate=20r/s;   

En mi caso tengo un archivo de configuración por cada sitio alojado en mi servidor /etc/nginx/sites-available

En server_name agregamos el dominio con el cual creamos el certificado, en ssl_certificate agregamos nuestro archivo fullchain.pem, en ssl_certificate_key agregamos el archivo privkey.pem

server {
listen *:80;
add_header Strict-Transport-Security max-age=15768000;
return 301 https://$host$request_uri;
}

Por último iniciamos el servicio de nginx, ahora todas nuestras peticones son por SSL

Puedes consultar tu dominio en la siguiente página, para validar el SSL de los ataques mas comunes

Si deseas aprender mas de .net core, servicios REST y Entity Framework puedes consultar mi gitbook gratuito aquí

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store