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>();
...
}