Usando ACS y tokens JWT para securizar aplicaciones web

El objetivo de este post y alguno que intentaré ir escribiendo más adelante es intentar explicar en detalle cómo es posible securizar tanto aplicaciones web ASP.NET MVC como servicios WebAPI empleando Windows Azure Access Control y  token JWT. Así mismo entraré en escenarios dónde se haga uso del nuevo servicio Windows Azure Active Directory.

En esta serie trataré los siguientes temás:

  • Cómo securizar aplicaciones web usando ACS y tokens JWT.
  • Desplegar aplicaciones web en Windows Azure WebSites que hagan uso de ACS.
  • Cómo securizar servicios WebAPI usando ACS y tokens KWT.
  • Cómo securizar una aplicación MVC que contenga tanto aplicaciones web como servicios WebAPI.
  • Cómo securizar aplicaciones web usando Windows Azure Active Directory ( WAAD ).
  • Cómo hacer uso del tenant de WAAD de Office 365 para securizar aplicaciones web.

En este primer post trataré el primero de los temas, sobre cómo es posible securizar una aplicación ASP.NET MVC haciendo uso de ACS y token JWT.

El primer paso para este ejemplo será crear un namespace de ACS desde el portal de Windows Azure. Una vez que hemos accedido a nuestra subscripción de Windows Azure tendremos que crear un namespace como se ve en la siguiente imagen:

DemoACS3

Una vez que tenemos el namespace podemos acceder a la administración del namespace y realizar la configuración que necesitemos para nuestra aplicación. En este caso, para simplificar el ejemplo, usaremos Windows Live Id como proveedor de identidad, pero como seguramente ya sabréis, es posible configurar múltiples proveedor, como un tenant de Windows Azure Active Directory (WAAD), un ADFS de un dominio on-premise o un tenant de Office 365 como veremos en post posteriores.

 

DemoACS4

Una vez que tenemos en namespace configurado, crearé una aplicación de ejemplo ASP.NET MVC 4 usando la plantilla de aplicación Internet.

DemoACS2

Para seguir con el ejemplo es importante asegurarnos de que en nuestro equipo de desarrollo tenemos instalado Windows Identity Foundation así como las herramientas para trabajar con Windows Azure Active Directory; Microsoft ASP.NET Tools for Windows Azure Active Directory – Visual Studio 2012. También antes de instalar las herramientas es necesario instalar ASP.NET and Web Tools 2012.2 Update

Una vez instalado estos componentes, si seleccionamos el proyecto web que acabamos de crear, veremos una nueva opción de menú “Identiy and Access…” que nos ayudará a securizar las aplicaciones web de una forma rápida y “sencilla”.

DemoACS5

Este asistente nos permite indicar el tipo de seguridad que queremos aplicar a la aplicación web, ya sea haciendo uso de ACS, de WAAD o de un STS local que podamos haber desarrollado nosotros. En el ejemplo que estamos desarrollando en este post elegiremos la última de las tres opciones que se nos presentan.

Una vez selecciona la opción de ACS tendremos que indicar el nombre de nuestro namespace, así como la key de administración que obtenemos del portal de Windows Azure. Indicando el nombre del namespace así como la key de administración el propio wizard podrá conocer los proveedores de identidad que tenemos configurados así como crear una “relaying party” para la aplicación que estamos desarrollando.

DemoACS7

DemoACS6

En este caso, como estamos desarrollando en nuestro entorno local, indicaremos las URL dónde nuestra aplicación ASP.NET MVC se hostea en nuestro servidor local. En  mi caso, http://localhost:28732/

Esta operación, entre otras cosas, lo que hace es crear una “relaying party” en namespace de ACS, en la cuál podremos configurar el tipo de token que queremos emplear, que como hemos comentado anteriormente, serán JSON Web Token  (JWT), un formato compacto que está cogiendo bastante popularidad gracias al hecho de que se puede usar desde diferentes lenguajes y plataformas…así como que es usando por empresas como Amazon (Fusion Middleware), Google (App Engine Security Module) o SalesForce en alguno de sus productos.

Así mismo, JWT es el formato que usa Windows Azure Active Directory y que también está soportado en modo Beta en Access Control, escenario que estamos viendo en este post.

DemoACS8

DemoACS9

Otro paso importante que no debemos olvidar es crear las reglas de ACS que queremos aplicar, para que a nuestra aplicación web puedan llegar los claims que nos interesen desde el proveedor de identidad.

En este caso estamos usando Windows Live ID, por lo que veremos que al generar las reglas únicamente se nos creará una nueva regla que hará que llegue a la aplicación en nameidentifier del usuario autenticado. El número y tipo de claims depende de cada proveedor de identidad.

DemoACS10

Una vez realizados estos pasos si intentamos ejecutar la aplicación (F5) veremos cómo una vez nos autenticamos en la aplicación con unas credenciales válidas de Windows Live ID nos encontramos con el siguiente error, que viene por el uso de JWT como formato de token.

ID4014: A SecurityTokenHandler is not registered to read security token (‘BinarySecurityToken’, ‘http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd’).

DemoACS11

Para poder solucionar este problema, necesitaremos crear nuestro propio SecurityTokenHandler que soporte token de tipo JWT.

El primero paso será añadir una referencia a System.IdentityModel, así como instalar a través de Nuget “JSON Web Token Handler For the Microsoft .Net Framework 4.5”, paquete que va a proporcionarnos diferentes clases para el manejo de tokens JWT.

DemoACS13

Una vez que lo tengamos instalado, tenemos que crear una nueva clases que herede de JWTSecurityTokenHandler.

  public class CustomJwtSecurityTokenHandler : JWTSecurityTokenHandler
    {
        public override ClaimsPrincipal ValidateToken(JWTSecurityToken jwt, TokenValidationParameters validationParameters)
        {
            if ((validationParameters.ValidIssuer == null) &&
                (validationParameters.ValidIssuers == null || !validationParameters.ValidIssuers.Any()))
            {
                validationParameters.ValidIssuers = new List<string> { ((ConfigurationBasedIssuerNameRegistry)Configuration.IssuerNameRegistry)
                    .ConfiguredTrustedIssuers.First().Value };
            }

            if (validationParameters.SigningToken == null)
            {
                validationParameters.SigningToken = new X509SecurityToken(new X509Certificate2(
                    GetSigningCertificate(ConfigurationManager.AppSettings["ida:FederationMetadataLocation"])));

            }
            return base.ValidateToken(jwt, validationParameters);
        }

        protected override string NameIdentifierClaimType(JWTSecurityToken jwt)
        {
            return ClaimTypes.GivenName;
        }

        public static byte[] GetSigningCertificate(string metadataAddress)
        {
            if (metadataAddress == null)
            {
                throw new ArgumentNullException(metadataAddress);
            }

            using (XmlReader metadataReader = XmlReader.Create(metadataAddress))
            {
                MetadataSerializer serializer = new MetadataSerializer()
                {
                    CertificateValidationMode = X509CertificateValidationMode.None
                };

                EntityDescriptor metadata = serializer.ReadMetadata(metadataReader) as EntityDescriptor;

                if (metadata != null)
                {
                    SecurityTokenServiceDescriptor stsd = metadata.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>().First();

                    if (stsd != null)
                    {
                        X509RawDataKeyIdentifierClause clause = stsd.Keys.First().KeyInfo.OfType<X509RawDataKeyIdentifierClause>().First();

                        if (clause != null)
                        {
                            return clause.GetX509RawData();
                        }
                        throw new Exception("The SecurityTokenServiceDescriptor in the metadata does not contain the Signing Certificate in the <X509Certificate> element");
                    }
                    throw new Exception("The Federation Metadata document does not contain a SecurityTokenServiceDescriptor");
                }
                throw new Exception("Invalid Federation Metadata document");
            }
        }
    } 

Una vez que tenemos la clase creada, a través del fichero de configuración, tendremos que hacer las modificaciones necesarias para que haga uso de la nueva clase que acabamos de crear: (sección securitytokenHandlers)

DemoACS16

y ahora sí, si ejecutamos la aplicación con F5 e introducimos unas credenciales válidas, veremos llegamos a ver la aplicación ASP.NET MVC!!

DemoACS15

Ibon Landa

bon Landa lleva más de 15 años dedicado al desarrollo de software. Durante este tiempo ha trabajado en diferentes empresas en las cuáles ha podido trabajar en diferentes entornos y tecnologías. Actualmente está focalizado principalmente en tareas de desarrollo, arquitectura, en las herramientas del ciclo de vida y en todo lo relacionado con la plataforma de Cloud Computing Microsoft Azure, área en el que ha sido reconocido como MVP. Participa de forma activa en la comunidad, escribiendo su blog, manteniendo un portal sobre Microsoft Azure y colaborando con Microsoft y grupos de usuarios en eventos de formación, talleres y giras de producto.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *