Las ventajas que te ofrece Microsoft Azure y el mundo.NET

Cómo securizar tus apps con Identity Server y .NET Core (Parte II)

Tomando el relevo del post escrito por el gran Adrián Díaz (Cómo securizar tus apps con Identity Server y .NET Core (Parte I)), quiero enseñaros cómo autenticar a los usuarios mediante usuario y contraseña. Empezamos creando nuestro servidor de identidades para centralizar tanto la autenticación como la autorización, y así crear una aplicación web que securizaremos mediante usuario y contraseña.

Al turrón… Configurando nuestro Identity Server para autenticar usuarios

En el anterior artículo vimos cómo configurar nuestro Identity Server. El cambio que haremos es definir los usuarios que pueden autenticarse. Para ello se van a definir recursos de identidad personalizados (los llamados “claims”) y usuarios en el arranque de la aplicación

public void ConfigureServices(IServiceCollection services)
{
     services.AddMvc();
     services.AddIdentityServer()
              .AddDeveloperSigningCredential()                    
              .AddInMemoryApiResources(Config.GetApiResources())
              .AddInMemoryClients(Config.GetClients())
              .AddInMemoryIdentityResources(Config.GetIdentityResources()) // damos de alta nuevos recursos de identidad
              .AddTestUsers(Config.GetUsers()); // damos de alta usuarios a autenticar
}

En la clase Config.cs definiremos los claims de identidad

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(), // claim estándar
        new IdentityResources.Profile(), // claim estándar
        new IdentityResources.Email(), // claim estándar
        new IdentityResource // claim personalizado
        {
            Name = "Name",
            UserClaims = new List<string> {"name"}
        },
        new IdentityResource // claim personalizado
        {
            Name = "Surname",
            UserClaims = new List<string> {"surname"}
        },
        new IdentityResource // claim personalizado
        {
            Name = "WorkPlace",
            UserClaims = new List<string> {"workplace"}
        }
   };
}

Y también los usuarios:

public static List<TestUser> GetUsers()
{
    return new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "adrian",
            Password = "elrealmadridcampeondeeuropaunavezmas"
        },
        new TestUser
        {
            SubjectId = "2",
            Username = "parrita",
            Password = "thesame"
        }
    };
}

Vamos a configurar un nuevo cliente que consumirá la API securizada por nuestro servidor de identidades, con un tipo de “grant” o concesión denominada: HybridAndClientCredentials, ya que vamos a permitir autenticar una persona por usuario y contraseña.

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "MyEncamina",
            // no interactive user, use the clientid/secret for authentication
            AllowedGrantTypes = GrantTypes.ClientCredentials,
            // secret for authentication
            ClientSecrets =
            {
                new Secret("++++++".Sha256())
            },
            // scopes that client has access to
            AllowedScopes = { "APIEmployee" }
        },
        new Client
        {
            ClientId = "openIdEncamina",
            ClientName = "Example Implicit Client Application",
            ClientSecrets = new List<Secret> { new Secret("superSecretPassword".Sha256()) },
            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
            AlwaysSendClientClaims=true,
            AllowedScopes = new List<string>
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Email,
                "APIEmployee”, "APICustomer”, 
            },
            RedirectUris = new List<string> { "https://localhost:44392/signin-oidc" },
            PostLogoutRedirectUris = new List<string> { "https://localhost:44392/" }
         }
    };
}

Si nos fijamos en la configuración del nuevo cliente, observaremos que existen dos URLs de redirección. Estas direcciones son las que usa Identity Server para redireccionar en caso de login correcto (RedirectUris) y en caso de realizar el logout de nuestra aplicación web.

NOTA: Es importante que la redirección de login correcto acabe en /signin-oidc ya que es un endpoint de callback definido en el protocolo OpenId.

Con esto, si iniciamos nuestro Identity Server y nos posicionamos en la siguiente URL/: .well-known/openid-configuration, se mostrará la nueva configuración (los claims de usuario tanto los expuestos por el estándar OpenId como los personalizados) y los nuevos ámbitos soportados.

En el código de ejemplo se ha definido una vista y controlador para realizar el login del usuario cuyo layout es el siguiente:

Con esto centralizamos y personalizamos nuestra página de solicitud de credenciales.

Configurando nuestra aplicación web para autenticar usuarios

Lo primero que se debe hacer es instalar el paquete NuGet IdentityServer4.AccessTokenValidation y, dentro de nuestro proyecto, añadiremos el siguiente código en el Startup.cs, para configurar el arranque de la aplicación web.

Agregamos al contenedor de inyección de dependencias y agregamos la configuración para emplear el protocolo OpenId que pasaremos a explicar.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
    {
        options.Authority = "https://localhost:44384/";
        options.ApiName = "EncaminaAPI";
    });

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "cookie";
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("cookie")
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = "https://localhost:44384/";
        options.ClientId = "openIdEncamina";
        options.ClientSecret = "superSecretPassword";
        options.SignInScheme = "cookie";
        options.SaveTokens = true;
        options.ResponseType = "code id_token";
        options.GetClaimsFromUserInfoEndpoint = true;

        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("APIEmployee"); // ámbito al cual se va a poder conectar este cliente OpenId
                 
    });


    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Con AddAuthentication, se agrega el servicio de autenticación en DI. Usaremos como elemento principal de autenticación la validación de Tokens Bearer. Más tarde, agregamos los esquemas de validación de cookie y de oidc (ya que esto indica que vamos a necesitar el esquema de OpenId para el login del usuario).

Con AddCookie añadimos el manejador o handler que procesa las cookies.

Finalmente, AddOpenIdConnect configura el manejador del protocolo OpenId. La parte más importante es el Authority que indica el servidor de identidades de confianza, y SaveTokens para guardar el token que devuelve Identity Server en la cookie.

A continuación, toca hacer uso de la autenticación definida en el método anterior en el pipeline HTTP.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
             
    // hacemos uso de autenticación
    app.UseAuthentication();

    app.UseStaticFiles();
    app.UseMvcWithDefaultRoute();
}

En el punto de entrada de nuestra aplicación Web (por defecto el controlador Home), activamos la seguridad empleando para ello el atributo [Authorize]. Esto provoca que al acceder a dicho controlador, se verifique si existe o bien token Bearer o una cookie de identidad y si no existiera, la solicitud se redirige a la página de Login de Identity Server para autenticar el usuario vía OpenId.

Iniciando primero nuestro servidor de identidades y después nuestra aplicación web obtenemos algo parecido a la imagen (fijaos bien en la url de la aplicación Web)

Nos identificamos, y voilá, accedemos a nuestra aplicación web y podemos ver el token que nos ha generado Identity Server.

 

Ya que soy un poco vaguete, he puesto el token en esta página para poder copiarlo y verificar que es un token bueno 😊.

CONCLUSIONES

En este post hemos comprobado que podemos configurar nuestro Identity Server para autenticar usuarios de nuestra web. En siguientes artículos os enseñaré cómo definir políticas de autorización en nuestras APIs y cómo combinar el poder de Identity Server con el poder de AspNet Identity y los configuraremos en base de datos empleando para ello Entity framework Core.  También veremos cómo autenticar vía proveedor externo como Twitter o Google.

Mientras tanto HAPPY CODING!

mm

Sobre Sergio Parra Guerra

Sergio Parra es Ingeniero Técnico en Informática de Sistemas por la UPSAM. Tiene a sus espaldas muchísimas certificaciones entre las cuales Microsoft Certified Professional y ex Microsoft MVP Visual Studio and Development Technologies. Actualmente es un magnífico Software & Cloud Architect en ENCAMINA.
Esta entrada ha sido publicada en .NET, seguridad. Enlace permanente.
Suscríbete a Piensa en Sofware desarrolla en Colores

Suscríbete a Piensa en Sofware desarrolla en Colores

Recibe todas las actualizaciones semanalmente de nuestro blog

You have Successfully Subscribed!

ENCAMINA, piensa en colores