Securizar aplicaciones Windows 8 con ACS

Después de unos cuántos post sobre gestión de identidad y seguridad basada en claims, en esta nueva entrada toca hablar sobre cómo securizar aplicaciones Windows 8, escenario que es algo distinto a los que hemos visto hasta ahora.

En muchos casos, cuando realizamos aplicaciones Windows 8 éstas necesitan hacer uso de servicios de backend, WebAPI por ejemplo, los cuáles pueden estar securizados empleado Windows Azure Active Directory (WAAD) o Windows Azure Access Control, tal y como hemos visto en los post anteriores. Cualquiera de las dos opciones pueden ser válidas para el caso dónde nos encontramos.

En cualquiera de los dos escenarios, la aplicación Windows 8 tendrá que pedir al usuario que se autentique contra el proveedor de identidad configurado para que una vez autenticado la aplicación pueda hacer llamadas a los servicios de backend.

En el post sobre cómo securizar un servicio WebAPI veíamos cómo incluir la seguridad en el servicio y cómo poder hacer un cliente C# que llame a este servicio, pidiendo al usuario las credenciales de autenticación. En ese ejemplo hacíamos uso de Windows Azure Authentication Library para simplicar la seguridad. El código era este:

 

var authContext = new AuthenticationContext("https://estoyenlanube.accesscontrol.windows.net");
AssertionCredential credential = authContext.AcquireToken("http://localhost:29350/");
var token = credential.CreateAuthorizationHeader();

HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", token);

var response = await httpClient.GetStringAsync("http://localhost:29350/api/values");

Si intentamos hacer lo mismo en una aplicación Windows 8, veremos cómo también está disponible la librería Windows Azure Authentication Library, pero con una gran diferencia, que ésta sólo funciona si el servicio WebAPI está securizado usando Windows Azure Active Directory, si usamos ACS, este código NO funcionará!

Entonces, si usamos Windows 8 y WAAD el código será prácticamente igual, salvo que el método AcquireToken nos pedirá un ClientID que podemos encontrar dentro del portal de Windows Azure.

y entonces….Qué pasa si usamos ACS para la securización? Pues que el tema se complica un poco; Tendremos que hacer uso del WebAuthenticationBroker y hacer alguna pequeña modificación en nuestro servicio WebAPI.

A modo de resumen, el proceso de autenticación que implementaremos será el siguiente:

  • La aplicación Windows 8 usará WebAuthenticationBroker, pidiendo a ACS que autentique el usuario.
  • El usuario tiene que introducir las credenciales para el proveedor de identidad configurado.
  • Una vez que el ACS autentica al usuario, éste le debe redirigir a un controlador del servicio WebAPI.
  • El servicio WebAPI generará un token de autenticación, que devolverá a a la aplicación Windows 8.
  • La aplicación Windows 8 podrá usar este token para hacer las llamadas al servicio WebAPI.

Ahora el código…

En la aplicación Windows 8 el código sería similar al siguiente:

 

WebAuthenticationResult webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(
                WebAuthenticationOptions.None,
                new Uri("https://estoyenlanube.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=https://estoyenlanube.azurewebsites.net/&wreply=https://estoyenlanube.azurewebsites.net/api/federation"),
                new Uri("https://estoyenlanube.azurewebsites.net/api/federation/end"));

            if (webAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
            {
               

 

En este ejemplo, el namespace del ACS es “estoyenlanube”, mientras que el servicio WebAPI lo tengo desplegado en https://estoyenlanube.azurewebsites.net. Lo tengo desplegamos en un servidor real porque ACS no es capaz de redirigir a direcciones localhost.

https://estoyenlanube.accesscontrol.windows.net:443/v2/wsfederation?wa=wsignin1.0&wtrealm=https://estoyenlanube.azurewebsites.net/&wreply=https://estoyenlanube.azurewebsites.net/api/federation

Con esta URL le estamos pidiendo al WebAuhenticationBroker que autentique usando el namespace estoyenlanube y el relaying party https://estoyenlanube.azurewebsites.net, Así mismo, le decimos que una vez autentique al usuario redirija la petición a un controlar del servicio WebAPI, el cuál generará el token.

El servicio WebAPI recibirá la petición y generará el token para que el cliente Windows 8 lo obtenga:

public class FederationController : ApiController
    {
        [HttpPost]
        public HttpResponseMessage Post()
        {
            var response = this.Request.CreateResponse(HttpStatusCode.Redirect);
            response.Headers.Add("Location", "/api/federation/end?token=" + ExtractBootstrapToken());
            return response;
        }

        protected virtual string ExtractBootstrapToken()
        {
            var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;
            JWTSecurityToken jwt = bootstrapContext.SecurityToken as JWTSecurityToken;
            return jwt.RawData;
        }
    }
 
 

En el fichero de configuración del servicio WebAPI también es necesario cambiar la configuración para que el controlador pueda generar el token:

<identityConfiguration saveBootstrapContext="true">

Finalmente, el cliente Windows 8, si la autenticación es correcta, podrá obtener el valor del parámetro token y usarlo para realizar las llamadas correspondientes al servidor:

var token = webAuthenticationResult.ResponseData.Substring(webAuthenticationResult.ResponseData.IndexOf("token=", StringComparison.Ordinal) + 6);

        HttpClient httpClient = new HttpClient();

        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", "Bearer " + token);

        var response = await httpClient.GetStringAsync("http://localhost:29350/api/values");

 

Y ya tenemos todo el proceso 🙂

Cómo hacer uso del tenant de WAAD de Office 365 junto con ACS

Como ya comentaba en el post anterior, Office 365 hace uso de Windows Azure Active Directory para la autenticación, por lo que si posees ya una cuenta de Office 365 ya dispones de un tenant de WAAD para securizar tus aplicaciones.

Si por ejemplo estás autenticado en tu subscripción de Office 365 y vas a http://activedirectory.windowsazure.com podrás ver la información y configuración de tu tenant de WAAD, pero…¿Se puede ver dentro del portal de Windows Azure como veíamos en el post anterior?

DemoACS37

Sí, se puede ver dentro del portal de Windows Azure, de dos maneras:

  • Creando una subscripción de Windows Azure asociada al administrador de la subscripción de Windows Azure.
  • Llamando a centro de soporte para que asocien una subscripción que ya tenga con el usuario administrador de Office 365.

Una vez hecho uno de estos pasos, si estas autenticado en Office 365 con el usuario administrador y vamos al portal de Windows Azure, podremos ver el tenant de WAAD tal y como veíamos en el post anterior.

Sea como sea, en este post vamos a ver cómo podemos usar el tenant de WAAD de Office 365 como un proveedor de identidad de ACS, para lo cuál no necesitamos que el tenant se vea en el portal de Windows Azure.

¿Qué conseguimos con esto?

Si configuramos este proveedor de identidad, podríamos securizar una aplicación web o WebAPI, tal y como ya hemos visto, desplegarlas en Windows Azure y usar los usuarios de Office 365 para autenticarse en todas las aplicaciones, con Single Sign On entre ellas claro.

Si además nos encontramos en un escenario real corporativo, seguramente tendremos el Office 365 sincronizado con nuestro dominio corporativo, por lo que es una manera de usar nuestras credenciales corporativas para logearnos en cualquier aplicación, ya esté en Office 365 o desplegada directamente en Windows Azure.

Desde el portal de Windows Azure, desde el namespace de ACS que hemos estado usando en el resto de ejemplo, añadiremos un nuevo proveedor de identidad de tipo “WS-Federation”.

DemoACS49

E indicaremos la URL dónde están los metadatos:

https://accounts.accesscontrol.windows.net/nombredeltenant.onmicrosoft.com/FederationMetadata/2007-06/FederationMetadata.xml

DemoACS50

Así mismo, podremos configurar las diferentes aplicaciones para que usen los proveedores de identidad que queramos. Hasta ahora habíamos usado siempre Windows Live ID.

DemoACS51

Por último, tenemos que configurar el tenant de Office 365 para que permite conexiones del ACS para la autenticación.

Para poder hacer esta configuración no tenemos interfaz de usuario y necesitamos hacer uso las “Windows Azure Active Directory Module for Windows Powershell” para poder lanzar estos comandos y realizar la configuración:

connect-msolservice 
import-module msonlineextended –force 
$replyUrl = New-MsolServicePrincipalAddresses -Address "https://[yournamespace].accesscontrol.windows.net/" 
New-MsolServicePrincipal -ServicePrincipalNames @("https://[yournamespace].accesscontrol.windows.net/")  -DisplayName "[displayName]" -Addresses $replyUrl

Y con estos pasos, ya podremos usar las credenciales que estuviéramos usando en Office 365 para autenticarnos.

DemoACS47

Securizar aplicaciones web usando Windows Azure Active Directory

Hasta ahora hemos visto varios ejemplos de securización de aplicaciones dónde siempre hemos hecho uso de Windows Azure Access Control. En este post hablaremos de Windows Azure Active Directory el cuál puede usarse de forma independiente a ACS o como un proveedor de identidad más de ACS.

Como en los ejemplos anteriores, partiremos de una aplicación ASP.NET MVC 4, en la cuál queremos incluir un mecanismo de seguridad basado en WAAD.

Para ello, lo primero, necesitaremos crear a través del portal de Windows Azure un tenant de WAAD. Tened en cuenta que los tenant van asociados a los usuarios, no a la subscripción, y sólo se permite uno por usuario. Además, a día de hoy no se puede borrar una vez creado…

Comentar que Office 365 usa WAAD, por lo que si ya tenéis una cuenta de Office 365 ya tenéis un tenant de WAAD que podrías usar, tal y como veremos en el siguiente post.

DemoACS38

Desde este menú podremos crear nuestro tenant, indicando los datos del mismo.

DemoACS39

Una vez creado, podremos crear usuarios, usuarios que son los que usaremos para autenticarnos en la aplicación.

Y si lo preferimos, podemos sincronizar el tenant que acabamos de crear con un Active Directory que tengamos on-premise y así usar los mismos usuarios que tengamos ya creados.

DemoACS40

Una vez que tenemos los usuarios, podemos dar de alta las aplicaciones que queremos securizar.

DemoACS43

e indicar el identificar único de la aplicación, como la URL dónde está desplegado. Si estamos probando en local la URL será algo como http://localhost:29350/

DemoACS44

Una vez hecho estos pasos iremos a la aplicación web y volveremos a hacer uso de la herramienta que nos permite configurar la seguridad basada en WIF.

DemoACS45

En este caso, indicaremos que queremos usar Windows Azure Active Directory, indicando el identificador único de la aplicación que hemos puesto anteriormente, así cómo la URL a los metadatos del STS. Esta URL se obtiene desde el portal de Windows Azure, dentro de la aplicación que hemos creado.

DemoACS46

DemoACS42

Y una vez hecho estos pasos, si ejecutamos la aplicación (F5) veremos cómo se nos redirige a la página de login dónde deberemos introducir las credenciales del un usuario de WAAD o de nuestro dominio si es que lo tuviéramos sincronizado.

DemoACS47

Cómo securizar una aplicación que contenga tanto aplicaciones web como servicios WebAPI

En los ejemplos anteriores hemos visto cómo securizar una aplicación ASP.NET MVC y una aplicación WebAPI haciendo uso de ACS y tokens JWT.

¿Pero qué pasa si en el mismo proyecto tenemos las dos cosas? Una aplicación web a la cuál un usuario puede acceder a través del navegador y un servicio WebAPI que se usa tanto desde el javascript de la aplicación web, como desde otros clientes como pueden ser una aplicación Windows 8 o Windows Phone.

En este caso, tendremos que hacer los pasos vistos en los dos post anteriores, pero veremos cómo una vez juntos no van a funcionar las llamadas al servicio WebAPI, salvo las propias que podamos hacer desde el javascript de la aplicación.

Cuando hacemos uso de WIF para autenticar la aplicación web, TODAS las llamadas que se realizar deben estar autenticadas. En caso de no estar autenticadas, se nos redirige a la pantalla de login para que podamos introducir las credenciales.

Si las llamadas las intentamos realizar por código, por ejemplo desde una aplicación Windows 8 haciendo uso de httpClient, estas llamadas serán igualmente redirigidas a la pantalla de login, aunque mandemos en las cabeceras el token de seguridad.

Solución? En mi caso lo he solucionado de la siguiente manera:

Lo primero es añadir la posibilidad de llamar a los servicios WebAPI a través de dos rutas diferentes. Una de las rutas será usada por la aplicación web, cuando desde el código JavaScript quiera llamar a estos servicios, mientras la otra ruta será usada para el resto de clientes; Windows 8, Windows Phone…

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.Routes.MapHttpRoute(
                name: "DefaultApiJs",
                routeTemplate: "apijs/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

En el fichero de configuración establezco que la ruta que va a ser usada por las “aplicaciones externas” permita llamadas anónimas.

  <location path="api">
    <system.web>
      <authorization>
        <allow users="*" />
      </authorization>
    </system.web>
  </location>

y por último, en el DelegationHandler haré que sólo se valide el token si la llamada no es autenticada.

if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
            {
                return base.SendAsync(request, cancellationToken);
            }
 

De esta manera conseguimos:

Si un usuario accede a través del navegador se le pedirán las credenciales para poder entrar en la web. Una vez autenticado, todas las llamadas que se hagan desde el JavaScript ya estarán autenticadas, por lo que no es necesario que el DelegationHandler haga nada.

Cuando hacemos por código desde una aplicación externa, haremos uso de la ruta que permite llamada anónimas y así evitar que se nos mande a la página de login. En este caso, el DelagationHandler sí tendrá que validar el token de seguridad y establecer la identidad en la llamada.

Cómo securizar servicios WebAPI usando ACS y tokens JWT

En el primer post de la serie veíamos cómo es posible securizar una aplicación web ASP.NET MVC como ACS y token JWT. En este post haremos un ejemplo similar, pero securizando un servicio ASP.NET WebAPI al cuál queremos llamar desde un cliente de forma segura.

DemoACS23

Una vez creada, veremos cómo ésta aplicación tiene unos controladores de ejemplo que podemos usar para el ejemplo que estamos mostrando, ya que lo que nos importante en este caso es cómo securizar el acceso.

Con el proyecto de ejemplo, si ejecutamos la aplicación con F5 podemos acceder a los controladores WebAPI a través del protocolo GET desde el propio navegador y ver los resultados que devuelve. Al no tener seguridad cualquiera puede consultarlos.

DemoACS24

DemoACS25

Una vez que tenemos la aplicación de ejemplo, como en caso anterior, será necesario crear un namespace de ACS así como una relaying party con las URLs dónde estamos desplegando el servicio WebAPI en local.

DemoACS26

Para seguir con el ejempo, el siguiente 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.

Una vez realizado estos pasos, la principal diferencia con el ejemplo de MVC es que aquí no podremos hacer uso de la herramienta “Identity and Access…” que veíamos en el post anterior y la cuál nos permitía configurar nuestra aplicación para hacer uso de ACS.

En este caso tendremos que desarrollar nuestro propio DelegatingHandler para que poder validar en todas las peticiones que se realicen al servicio WebAPI que el  cliente está debidamente autenticado contra el proveedor de identidad y STS que tengamos configurado en nuestro ACS.

A través del Global.asax añadiremos nuestro validador personalizado, el cuál se encarga de asegurarse de que el token se envían en las cabeceras de todas las peticiones, que el token es correcto y es establecer la identidad para que desde los controladores WebAPI pueda acceder a toda la información del usuario autenticado, por ejemplo, los claims.

GlobalConfiguration.Configuration.MessageHandlers.Add(new TokenValidationHandler());
El código de nuestro handler sería el siguiente:
 internal class TokenValidationHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            HttpStatusCode statusCode;
            string token;

            if (!TryRetrieveToken(request, out token))
            {
                statusCode = HttpStatusCode.Unauthorized;
                return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
            }

            try
            {
                // Use JWTSecurityTokenHandler to validate the JWT token
                JWTSecurityTokenHandler tokenHandler = new JWTSecurityTokenHandler();

                List<string> issuers = new List<string>();
                issuers.AddRange(ConfigurationManager.AppSettings["Issuers"].Split(new[] { ',' }));

                // Set the expected properties of the JWT token in the TokenValidationParameters
                TokenValidationParameters validationParameters = new TokenValidationParameters()
                {
                    AllowedAudience = ConfigurationManager.AppSettings["AllowedAudience"],
                    ValidIssuers = issuers,

                    // Fetch the signing token from the FederationMetadata document of the tenant.
                    SigningToken =
                    new X509SecurityToken(new X509Certificate2(GetSigningCertificate(ConfigurationManager.AppSettings["ida:FederationMetadataLocation"])))
                };

                Thread.CurrentPrincipal = tokenHandler.ValidateToken(token, validationParameters);
                HttpContext.Current.User = Thread.CurrentPrincipal;

                return base.SendAsync(request, cancellationToken);
            }
            catch (SecurityTokenValidationException)
            {
                statusCode = HttpStatusCode.Unauthorized;
            }
            catch (Exception)
            {
                statusCode = HttpStatusCode.InternalServerError;
            }
            return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
        }

        // This function retrieves ACS token (in format of OAuth 2.0 Bearer Token type) from 
        // the Authorization header in the incoming HTTP request from the ShipperClient.
        private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
        {
            token = null;
            IEnumerable<string> authzHeaders;
            if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
            {
                // Fail if no Authorization header or more than one Authorization headers 
                // are found in the HTTP request 
                return false;
            }

            // Remove the bearer token scheme prefix and return the rest as ACS token 
            var bearerToken = authzHeaders.ElementAt(0);
            token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
            token = bearerToken.StartsWith("Authorization Bearer ") ? bearerToken.Substring(21) : bearerToken;
            return true;
        }

        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");
            }
        }

    }

Si una vez realizado estos cambios, volvemos a ejecutar la aplicación y realizamos una llamada desde el navegador, veremos cómo todas las llamadas pasan por nuestro validador personalizado, el cuál rechazará todas las llamadas que no contenga un token de seguridad validado.

DemoACS31

El último paso del ejemplo será realizar un cliente C# que sea capaz de llamar al servicio WebAPI pasando un token de seguridad válido para nuestro STS.

En este caso he creado un proyecto de Test, al que he añadid el paquete “Windows Azure Authentication Library”, el cuál simplifica enormemente el trabajo con WIF, ya sea con ACS o con Windows Azure Active Directory.

DemoACS32

El siguiente código muestra cómo es posible realizar una llamada al servicio WebAPI.

En el ejemplo se hace uso de la clase AuthenticationContext disponible en WAAL, en la cuál indicamos el namespace de ACS con el que estemos trabajando, así como el nombre de la relaying party para el cuál queremos obtener un token de seguridad.

El método AcquireToken nos mostraré una interfaz de usuario en función del proveedor o proveedores configurados en ACS, para que podamos autenticarnos.

Una vez autenticados, podremos generar un token para poder mandarlo en las cabeceras de autenticación. En este caso, el delegationHandler que hemos desarrollado anteriormente validará correctamente el token y establecerá la identidad en la llamada.

[TestClass]
    public class DemoWebAPITests
    {
        [TestMethod]
        [TestCategory("Integration")]
        public async Task TestWebAPIService()
        {
            var authContext = new AuthenticationContext("https://estoyenlanube.accesscontrol.windows.net");
            AssertionCredential credential = authContext.AcquireToken("http://localhost:29350/");
            var token = credential.CreateAuthorizationHeader();

            HttpClient httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", token);

            var response = await httpClient.GetStringAsync("http://localhost:29350/api/values");

        }
    }

DemoACS34

 

DemoACS36

Desplegar aplicaciones web en Windows Azure WebSites que hagan uso de WIF

Siguiente con la serie de posts dedicada a la securización de aplicaciones, este post veremos cómo desplegar en Windows Azure Web Sites la aplicación web desarrollada en el post anterior y un par de puntos que tendremos que tener en cuenta para que todo funcione sin problemas.

  • Cómo securizar aplicaciones web usando ACS y tokens JWT.
  • Desplegar aplicaciones web en Windows Azure WebSites que hagan uso de WIF.
  • 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

El primer paso es crear un nuevo Web Site desde el portal de WIndows Azure dónde desplegaremos la aplicación ASP.NET desarrollada en el post anterior y que hace uso de ACS y tokens JWT.

DemoACS17

Una vez creado el site, modificaremos en namespace de ACS para que en lugar de hacer uso las URL locales, haga uso de las URL de WIndows Azure dónde desplegaremos la aplicación.

En este caso estamos modificando la misma relaying party que teníamos ya, pero en un escenario real os recomendaría tener dos “relaying party”, una configurada para funcionar en local y otra para funcionar cuando esté desplegada en Windows Azure y hacer uso de las transformaciones que soporta web.config para que cuando ésta se despliega en Windows Azure la configuración sea modificada para tener la configuración adecuada.

DemoACS20

El siguiente paso, será utilizar el wizard de publicación que nos proporciona el Sdk de Windows Azure para publicar de manera directamente desde Visual Studio.

Este wizard de forma sencilla nos permite indicar las credenciales de nuestra subscripción Windows Azure, así como indicar el servicio sobre el que se quiere desplegar la aplicación, tal y como se ve en las imágenes siguientes:

DemoACS18

DemoACS19

Y una vez desplegada, si lo hacemos con los errores remotos activados ( <customErrors mode="Off"></customErrors> ) veremos el siguiente error!!

The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread’s user context, which may be the case when the thread is impersonating.

DemoACS21

Este error se produce por el uso DAPI, método por defecto para proteger las cookies en aplicaciones WIF y el cuál no está disponible en Windows Azure Web Sites.

Cambiarlo es sencillo, ya que la propia herramienta nos permite indicar que queremos usar un método alternativo, chequeando la opción “Enable Web farm ready cookies” en el pestaña de configuración del asistente “Identity and Access…”.

Sino podemos cambiar manualmente estas entradas en el fichero de configuración:

 <securityTokenHandlers>  
        <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
DemoACS17_2

Barrica Tech Day en Logroño!

El próximo 25 de mayo celebraremos un evento en Logroño dónde todos los que queráis estáis invitados a venir! Aquí os dejo todos los detalles del mismo.

Registro: http://barricatechday.eventbrite.com/ 

10:00 – 10:50 Desarrollo de aplicaciones web con Silex por Asier Marques (@asiermarques) de Simettric

Silex es un framework ágil y muy profesional para desarrollar aplicaciones web con PHP. Silex reduce el tiempo de desarrollo de aplicaciones pequeñas a horas y el de aplicaciones medianas a días.

Durante la sesión podremos ver:

  • ¿Por qué Silex?
  • Cómo trabajar con Silex.
  • Cómo escalar Silex.

10:00 – 10:50 Git por Fernando Perez  (@ferpega_) de Plain Concepts

Los repositorios de código se han convertido en una pieza fundamental del desarrollo de software hoy día.  Usarlos es esencial para cualquier proyecto, sea del tamaño que sea y esté formado por una sola persona o por varios equipos. 

En esta sesión veremos las diferencias entre repositorios de código centralizados y descentralizados y ahondaremos en la terminología y particularidades que han hecho de GIT el repositorio que cuenta con un mayor crecimiento hoy día, pasando después por una introducción a los comandos básicos de GIT y algunas utilidades gráficas que pueden facilitarnos la vida en nuestros inicios con este fantástico gestor de código fuente.

12:00 – 12:50 Montando un escenario de integración continúa por Ibon Landa (@ibonilm) de Plain Concepts

Durante esta sesión se mostrará de forma práctica cómo es posible montar un escenario de integración continua dónde se pueda construir el software cada vez que se realiza alguna modificación y pasar las pruebas de forma automatizada, a la vez que se hace uso de una plataforma de Cloud Computing para desplegarla durante el proceso.

Durante la sesión se empleará Azure WebSites como plataforma de Cloud Computing dónde desplegar las aplicaciones, plataforma que permite trabajar con GitHub, TFS Service, Bitbucket o CodePlex para crear escenarios de integración continua.

13:00 – 13:50 Pruebas funcionales en la web con CasperJS por Vicenç Garcia (@vgaltes) de Plain Concepts

Las pruebas funcionales son un mal necesario cuando estamos desarrollando software. Así como las pruebas unitarias nos ayudan a asegurar que estamos haciendo las cosas correctamente, las pruebas funcionales nos ayudarán a asegurar que estamos haciendo las cosas correctas.

Como toda prueba de nuestro sistema, es necesario que la podamos pasar tantas veces como sea necesario y con el menor coste para nosotros como sea posible, así que automatizarlas se antoja vital.

En esta sesión veremos cuando puede ser interesante hacer este tipo de pruebas y veremos cómo podemos apoyarnos en CasperJS para codificarlas.

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