Cómo asegurar aplicaciones con
OAuth + OpenID + IdentityServer4

Demian Sclausero
Flux IT Thoughts
Published in
11 min readJun 10, 2021

--

Desde hace un tiempo las aplicaciones comenzaron a distribuirse, a ser autónomas y a aplicar teorías de arquitecturas con contextos bien definidos como Microservicios. Estamos acostumbrados a desarrollar y utilizar aplicaciones donde la seguridad y el dominio de las mismas están juntos, lo que plantea nuevos desafíos a la hora de trabajar con tecnologías y arquitecturas en las que está todo distribuido (tanto las aplicaciones de los clientes como los diferentes backends), en distintos dominios, servidores y contextos.

Antes de hablar de OAuth 2.0 y OpenID Connect, es importante que tengas claro dos conceptos: el de autenticación y el de autorización.

La autenticación es el proceso de verificar una identidad; es decir, confirmar que una persona es quien dice ser. Normalmente, para constatar este hecho, el usuario usa algo que conoce para demostrar su identidad, como un usuario y una contraseña.

Por otro lado, la autorización es el proceso de verificar lo que un usuario puede hacer. Por ejemplo, un usuario puede incluir valores a una lista, pero no puede eliminar dicha lista. La autorización ocurre después de que un usuario se haya autenticado.

¿Y cuál es la finalidad de todo esto? Es la de acceder a nuestros recursos. Cuando hablamos de recursos, nos referimos a una lista de datos, un set de información, algo que brinda un servicio o una aplicación, que está guardado y que para poder acceder tenemos que, previamente estar autenticados y/o autorizados.

Podemos tener casos en los que los clientes se comuniquen entre sí o se comuniquen con una o más APIs (que, a la vez, se comuniquen con otras APIs). Y para poder solucionar esto es que se utilizan los tokens. En estos casos no nos es suficiente tener implementada la seguridad dentro de nuestras APIs, porque al haber varios clientes o APIs implicadas, la seguridad en nuestras APIs no puede satisfacer todos los requerimientos de todos los clientes. Consideremos, también, que hay métodos de autenticación que envían usuarios y contraseñas mediante http, haciendo super inseguras nuestras aplicaciones.

¿Cómo solucionamos el problema de tener muchas APIS contra varios clientes, y poder “movernos” entre ellas con seguridad? Con un Token. Los tokens están relacionados con el consentimiento, el permiso: un usuario le da poder a una aplicación cliente para acceder a una API y obtener recursos en su nombre.

Gracias a los tokens dejamos de usar las credenciales del cliente a nivel aplicación, ya que la seguridad va a estar centralizada, y por ende debemos introducir solo una vez usuario y contraseña. Éstas estarán ocultas y viajarán a través del token entre peticiones a distintas APIs, ya que los tokens son suficientemente seguros para la autenticación y autorización a las diferentes aplicaciones. Mientras el token está “vivo”, el acceso será garantizado

Entonces, la seguridad debe estar centralizada, y para eso debemos utilizar un servidor de autenticación que concentre todas las peticiones y oficie de validador de tokens. Es decir, todas las APIs y clientes le van a pedir a este IDP (servidor de identidad) que valide si los tokens recibidos son válidos, por lo que la responsabilidad de la seguridad ya cae en un solo lado: en el IDP.

De manera que se debe centralizar la gestión de los usuarios para tener un control único de usuarios y contraseñas. Esto nos lleva al Core de este artículo.

IdentityServer4, OpenID Connect y OAuth2 nos ayudarán en este tramo para poder manejar toda la parte de autenticación, autorización y creación de tokens, además de la validación de los mismos desde una API o un cliente.

OAuth2

Nace para poder satisfacer la necesidad de seguridad de aplicaciones distribuidas. OAuth2 es un protocolo de autorización, es decir que brindar acceso a un recurso. Esto es como si yo tuviera una entrada para asistir a un show en un teatro: a la persona que está en la puerta, lo único que le importa es que yo tenga el ticket para dejarme pasar. Esto es lo que hace OAuth2: no le importa quién soy sino que tenga el “ticket” (token) para darme acceso a un recurso. Provee una delegación de acceso seguro en nombre de un resource owner (dueño del recurso).

Echemos un vistazo a los componentes principales de OAuth:

  • Protected Resource: el recurso al que queremos acceder, una API.
  • Client: la aplicación que quiere acceder al recurso que está protegido, en nombre de alguien (puede ser una aplicación web, móvil, de escritorio, para smart TV, un dispositivo IoT, etc.).
  • Resource Owner: se trata del usuario. Se le llama el “propietario” de los recursos porque, si bien la API no es suya, sí lo son los datos que maneja.
  • Authorization Server: es el responsable de gestionar las peticiones de autorización. En nuestro caso, IdentityServer4

Estas cuatro partes se relacionan entre sí de la siguiente manera:

Cómo funciona OAuth 2.0

  1. La aplicación solicita al usuario que se autentique.
  2. El usuario es redirigido al servidor de autorización para su identificación. Éste puede loguearse a través de usuario y contraseña, de reconocimiento facial o de voz, puede tener un segundo factor de autenticación o incluso se le puede permitir el acceso a través de otras cuentas propias, como Google, Facebook, Amazon, etc. Una vez que el usuario es validado, debe estar de acuerdo con lo que la aplicación quiere hacer (esto se llama consentimiento). Normalmente se muestra una lista de los permisos que la aplicación está solicitando.
  3. Cuando la identidad del usuario es validada de manera satisfactoria, y éste ha consentido lo que se quiere hacer con esta autorización, es redirigido de nuevo a la aplicación cliente, otorgándole un código confirmando le autoriza hacer cosas por él en el recurso protegido
  4. La aplicación cliente hace una solicitud al servidor de autorización, con el permiso que el usuario le ha dado, además de algunos datos que identifican a la aplicación a fin de verificar que es un cliente válido para acceder a los recursos que se propone.
  5. Si todo va bien, el servidor de autorización responderá con un token de acceso (access token en inglés).
  6. Con este access token, la aplicación cliente será capaz de realizar llamadas a la API que necesita acceder para llevar a cabo su cometido.
  7. Cuando el recurso protegido recibe el access token necesita verificar de alguna forma. Una vez validado, comprobará si el usuario tiene los permisos por los cuales se hizo la petición y, si está todo ok, la API responderá con los datos que se le han pedido.

Ahora que sabemos el Estándar de OAuth2 podemos hablar de flujos de autorización de OAuth2.

Desde https://aaronparecki.com/oauth-2-simplified/#authorization podemos ver todos los flujos de autorización que existen.Pero, ¿qué es un flujo?

En OAuth2 los flujos determinan cómo se devuelve el token. Pero antes debemos determinar dos tipos de clientes: el cliente confidencial y el cliente público (a veces llamados de Back Channel o de Front Channel).

El cliente confidencial es todo aquel cliente que puede mantener la confidencialidad de sus credenciales y autenticarse de manera segura. Esto es así porque guarda sus credenciales de manera segura sin el acceso al usuario.

El cliente que no puede garantizar lo anterior es un cliente público. Un ejemplo de esto es una aplicación de JavaScript que corre en un navegador, porque no se puede autenticar de manera segura.

Existen varios flujos definidos para poder llevar a cabo la autenticación de manera segura pero los comunes son:

A. Implicit flow

  1. Ya está deprecado.
  2. Es interactivo.
  3. Es para webs, solo para SPA. El token queda almacenado en el navegador.
  4. El callback URL es un dato muy importante
  5. El token debe tener un vencimiento largo: no se permite refrescar porque sería peligroso.

B. Authorization code

  1. Es interactivo: una persona tiene que ingresar sus credenciales
  2. Es para webs: utiliza redirección.
  3. Ideal si tiene un back end.
  4. Se recomienda para reemplazar implicit.
  5. Se genera un code en Front Channel (comunicación no segura).

C. Authorization code + PKCE

  1. Es el más seguro.
  2. Es una extensión de CODE.
  3. Se agrega en el request inicial un challenge y un method.
  4. Para solicitar el token se incluye el code verifier y el method.

D. Client credentials

  1. No sirve para ser usado por un usuario: es para comunicarse entre aplicaciones o entre APIs, siempre por un Back Channel (comunicación segura).
  2. Soporta refresh token.
  3. Soporta comunicación por Back Channel (comunicación segura).

E. Hybrid

  1. Es una combinación de Implicit Flow y Authorization CODE
  2. Permite utilizar una combinación de token de identidad, token de acceso y código a través del canal front, utilizando un redireccionamiento codificado por fragmentos (clientes nativos y basados ​​en JS) o forms (aplicaciones web basadas en servidor),
  3. Los tokens son devueltos desde el endpoint de authorization y de token, dependiendo del response_type en la petición del authorization endpoint.

F. Resource owner

  1. Se utiliza para flujos de legacy.
  2. Es el único flujo que funciona sin browsers.
  3. Es necesario manipular las credenciales del usuario.
  4. Soporta refresh token.

Los más apropiados son los authorization CODE + PKCE (el famoso PIKY)

Hay varios tipos de tokens, pero los más comunes son JWT(Json Web Token), Reference Token y, en algunos flujos, el Refresh Token.

JWT nos permite una transmisión de datos seguros dado que los mismos son JSON.

Es un token auto validado, ya que una parte de la firma controla que el token no haya sido alterado.

Un JWT tiene 3 partes principales:

  1. Header: contiene el algoritmo de encriptado y el tipo de token.

2. Payload: contiene la Información del token propiamente dicha. Aquí veremos los claims que enviaremos en el token y los datos de expiración del mismo.

3. Verify Signature: se utiliza para verificar si el token es válido o no. Aquí se verifica con la firma y se certifica la autenticidad del token, validando que el mismo no haya sido alterado.

Al estar autofirmado y no necesitar de una entidad para su validación, el JWT no permite un revoke: sólo se espera a que su tiempo caduque; por eso es que se utiliza con tiempos de caducidad bajos.

A diferencia del JWT, el Reference Token tiene que estar convalidado con el IDP. Y por eso soporta el revoke.

Y por último, el Refresh Token es usado para generar un nuevo access token. Si el access token expira, el usuario tendría que autenticarse otra vez para obtener un access token nuevo. El Refresh Token genera que este paso se saltee, y con una petición a la API obtiene un nuevo access token que permite al usuario seguir accediendo a los recursos de la aplicación.

El Refresh Token requiere una seguridad mayor que el Acces Token a la hora de ser almacenado ya que, si fuera sustraído, podrían utilizarlo para obtener un access token y acceder a los recursos protegidos. Para poder evitar un escenario como este debe implementarse en el servidor algún sistema que permita invalidar un Refresh Token, además de establecer un tiempo de vida que obviamente debe ser más largo que el de los Access Token.

OpenID Connect

Dijimos que OAuth permite el acceso a recursos y a distintas aplicaciones. Pero, ¿qué pasa si no sólo quiero acceder, sino que también necesito saber si el usuario que está accediendo es quien dice ser? Para eso se agrega sobre OAuth una capa más. Siguiendo con el ejemplo de la entrada para el teatro, es como decir que necesito saber si la persona que tiene el ticket para entrar es, efectivamente, Demian Sclausero.

¿Cómo lograrlo? Se agregan scopes definidos a la capa de OAuth, los cuales definen información propia del usuario. Eso permite obtener información del usuario, como el mail, el ID, etc.

Este protocolo define información propia del usuario y aplica otras políticas de acceso. Por ejemplo, el acceso por roles.

IdentityServer4

Una vez que tenemos en claro cuál es la teoría y cómo se comportan OAuth2 y OpenID debemos considerar la tercera pata: quien provee ese mecanismo físico de autenticación. Y para eso se creó IdentityServer4.

IdentityServer es un middleware compatible con las especificaciones de OpenID Connect y OAuth 2.0, visibilizando los end points para la obtención de tokens y el manejo de la seguridad.

Cuando se desarrolla una aplicación que contiene páginas login y logout (y hasta una página de consentimientos, dependiendo de las necesidades), el middleware de IdentityServer cumple las necesidades, agregando los endpoints para poder comunicarse de forma segura y cumplir con los estándares requeridos.

Para iniciar con IdentityServer4, .Net permite descargar una plantilla con todo lo necesario para el desarrollo.

Para ello debemos escribir en una consola:

Esto mostrará todos los templates que tenemos disponibles para descargar:

Aquí vemos los templates disponibles para empezar con IdentityServer4. Una vez decidido qué template necesitamos, debemos descargarlo y comenzar a usarlo.

Con el comando dotnet new is4ef -n AerolineasSecurity estamos descargando IdentityServer4 y nombrando el proyecto como “AerolineasSecurity”.

Una vez descargado, abrimos el proyecto y tenemos la estructura básica.

La clase Config.cs tiene el primer aproach de configuración del Identity, donde podemos ver:

  1. Los scopes: son los delimitadores de acceso. Cada scope indica a dónde voy a poder ingresar.
  2. Los clientes: son quienes definen los flujos.

3. En este caso podemos ver un flujo de ResourceOwnerPassword (el atributo AllowedGrantTypes es el que determina el flujo de acceso) utilizado para acceder por usuario y contraseña. El mismo tiene definidos sus CORS, y sus configuraciones de tiempo y ciclo de vida, así como la lista de los scopes permitidos. Además podemos ver los predefinidos por IDS4, la utilización de OpenID y el Offline_Access que determina el uso del Refresh Token.

Cuando trabajamos con aplicaciones distribuidas, tanto en dominios como en servers, es muy difícil mantener la seguridad de cada una de esas distribuciones. Para soportar y solucionar este problema nace OAuth, que en su versión 2 dispone de una abstracción de seguridad para el manejo de todos los componentes intervinientes, delegando el permiso del usuario en un token. Esto es la autorización.

Y si además quiero saber quién es el que está consumiendo ese servicio, debe tener autenticación. Esto se hace mediante OpenID, que aporta una capa sobre OAuth para poder identificar el usuario.

Pero esto no puede existir sin un soporte físico, un servidor de identidad. Y para eso nace IdentityServer, que en su versión 4 es el middleware que conecta al usuario con la seguridad.

Conocé más sobre Flux IT: Website · Instagram · LinkedIn · Twitter · Dribbble · Breezy

--

--