Codeblox Blog

Login OAuth simples com senha utilizando IdentityServer4

Guilherme
10/09/2021

O padrão OAuth implementado pelo IdentityServer4 possui um fluxo de login por senha (ROPC) que apesar de obsoleto na versão mais atual do padrão, ainda é bastante útil quando temos como requisito um login por senha onde o provedor de autenticação é o próprio Backend da aplicação (considerando uma solução de WebApis consumidas por um SPA por exemplo).

Inicie instalando o IdentityServer4 no seu projeto ASP.NET

dotnet add package IdentityServer4

Esta aplicação fará tanto o papel de Provedor da Autenticação, quanto de Consumidor.

Vamos iniciar com as configurações para o papel de “Provedor”:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    RSA rsa = RSA.Create();
    rsa.ImportRSAPrivateKey(
        source: Convert.FromBase64String(CHAVE_PRIVADA),
        bytesRead: out int _
    );

    services.AddScoped<IResourceOwnerPasswordValidator, ValidadorDeSenha>();
    var identityServerBuilder = services.AddIdentityServer();

    identityServerBuilder
        .AddSigningCredential(new SigningCredentials(
            key: new RsaSecurityKey(rsa),
            algorithm: SecurityAlgorithms.RsaSha256
        ))
        .AddInMemoryIdentityResources(
            new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
            }
        )
        .AddInMemoryClients(new Client[] {
              new Client {
                ClientId = "client-id-do-frontend",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                AllowAccessTokensViaBrowser = true,
                RequireConsent = false,
                RequireClientSecret = false,
                AllowOfflineAccess = true,
                AllowedScopes =
                {
                    LocalApi.ScopeName,
                    StandardScopes.OpenId,
                    StandardScopes.Profile,
                    StandardScopes.Email
                }
        }
    });
    ...
}

Agora o papel de consumidor

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
      services
        .AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            RSA rsa = RSA.Create();
            rsa.ImportRSAPublicKey(
                source: Convert.FromBase64String(CHAVE_PUBLICA),
                bytesRead: out int _
            );
            options.IncludeErrorDetails = true;
            options.Authority = configuration["Host"];
            options.TokenValidationParameters = new TokenValidationParameters
            {
                IssuerSigningKey = new RsaSecurityKey(rsa),
                ValidateAudience = false
            };
        });
     ...
}

Os valores para as chaves pública e privada, podem ser gerados por uma aplicação console utilizando o código abaixo:

using System;
using System.Security.Cryptography;

namespace RsaKeysGenerator
{
    class Program
    {
        /// <summary>
        /// Execute para gerar novas chaves (publica e privada) de RSA.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            using RSA rsa = RSA.Create();
            string pvt = Convert.ToBase64String(rsa.ExportRSAPrivateKey());
            string pub = Convert.ToBase64String(rsa.ExportRSAPublicKey());
            Console.WriteLine($"Chave privada: {pvt}");
            Console.WriteLine($"Chave pública: {pub}");
        }
    }
}

Implementar a lógica de verificação de login.

ValidadorDeSenha.cs:

public class ValidadorDeSenha : IResourceOwnerPasswordValidator
{
    public ValidadorDeSenha()
    {
    }

    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
      // Utilizar context.UserName e context.Password para validar sobre sua base de usuários
      bool sucesso = true;
      if(sucesso){
         context.Result = new GrantValidationResult(
                subject: "identificador do usuário da sua aplicação",
                authenticationMethod: "custom",
                claims: Array.Empty<Claim>()
            );
      }else{
          context.Result = new GrantValidationResult
            {
                IsError = true,
                Error = "motivo da falha no login"
            };
      }
    }
}

E por fim, registrar ele como serviço

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IResourceOwnerPasswordValidator, ValidadorDeSenha>();
    ...
}
aspnet
oauth
dotnet


Guilherme M. Abdo
Guilherme M. Abdo
Desenvolvedor fullstack .Net/Angular/Azure/GitHub