Autenticazione basata su certificati con ASP.NET Core

di Marco De Sanctis, in ASP.NET Core,

In uno scenario in cui dobbiamo effettuare chiamate server-to-server in modalità sicura, una possibile alternativa a OAuth2 è l'utilizzo di certificati X509. Con questo sistema, il client allegherà alla richiesta un particolare certificato, la cui thumbprint verrà poi verificata dal server.

Ci sono vari vantaggi nell'adozione di un sistema di questo tipo: se pensiamo per esempio a un'architettura basata su decine di micro servizi, tutti questi componenti possono condividere il medesimo certificato memorizzato su Azure KeyVault, così da semplificare scenari di rotazione delle chiavi.

Come implementare un sistema del genere in ASP.NET Core?

Per prima cosa abbiamo bisogno di generare due certificati di test:
1) una Certificate Authority
2) un certificato di test vero e proprio, generato a partire dalla CA precedente

Entrambi questi task possono essere eseguiti tramite il codice PowerShell in basso:

# Generiamo il certificato per la CA
$rootCert = New-SelfSignedCertificate -DnsName "localhost", "localhost" `
  -CertStoreLocation "cert:\CurrentUser\My" `
  -NotAfter (Get-Date).AddYears(20) `
  -FriendlyName "CertDemoRoot" `
  -KeyUsageProperty All `
  -KeyUsage CertSign, CRLSign, DigitalSignature

$rootThumbprint = $rootCert.Thumbprint

$mypwd = ConvertTo-SecureString -String "0000" -Force -AsPlainText

Get-ChildItem -Path "cert:\CurrentUser\My\$rootThumbprint" `
  | Export-PfxCertificate -FilePath .\CertDemoRoot.pfx -Password $mypwd > $null

# Installiamo il CA Cert nei TrustedRoot
Import-PfxCertificate -FilePath .\CertDemoRoot.pfx `
  -CertStoreLocation "cert:\CurrentUser\Root" `
  -Password $mypwd > $null

# Creiamo il client certificate e installiamo su My
$clientCert = New-SelfSignedCertificate `
  -certstorelocation cert:\CurrentUser\my `
  -dnsname "localhost" `
  -Signer $rootCert `
  -NotAfter (Get-Date).AddYears(20) `
  -FriendlyName "CertDemoClient"
 
$clientThumbprint = $clientCert.Thumbprint

Get-ChildItem -Path "cert:\CurrentUser\my\$clientThumbprint" `
  | Export-PfxCertificate -FilePath .\CertDemoClient.pfx -Password $mypwd > $null

# Rimuoviamo il CA Cert da My
Get-ChildItem -Path "cert:\CurrentUser\My\$rootThumbprint" | Remove-Item > $null

Write-Host "Thumbprint: $clientThumbprint"

Grazie a questo script, avremo nel nostro store personale due nuovi certificati pronti all'uso. Ora non dobbiamo far altro che creare un progetto ASP.NET Core Web API, e aggiungere una reference al package:

Microsoft.AspNetCore.Authentication.Certificate

A questo punto, possiamo iniziare a modificare il nostro metodo Main come segue:

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);

    // .. altro codice qui ..
    builder.Services.Configure<KestrelServerOptions>(options =>
    {
        options.ConfigureHttpsDefaults(options =>
        {
            options.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
        });
    });

    var cert = new X509Certificate2(@"C:\[folder]\CertDemoClient.pfx", "0000");
    builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(options =>
        {
            options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
            options.Events = new CertificateAuthenticationEvents()
            {
                OnCertificateValidated = ctx =>
                {
                    if (ctx.ClientCertificate.Thumbprint == cert.Thumbprint)
                    {
                        Console.WriteLine("Certs match!");
                        ctx.Success();
                    }
                    else
                    {
                        Console.WriteLine("certs don't match");
                        ctx.Fail("wrong cert");
                    }

                    return Task.CompletedTask;
                }
            };
        });

    builder.Services.AddAuthorization();

    // .. altro codice qui ..

    app.UseAuthentication();
    app.UseAuthorization();

    // .. altro codice qui ..
}

Il codice in alto è un estratto del metodo Main, dove per prima cosa andiamo a configurare Kestrel per accettare un certificato dal client attivando la modalità AllowCertificate. Questa è solo una delle due opzioni utili (l'altra è RequireCertificate) e, nello specifico, è utile se vogliamo far sì che quella basata su certificate non sia l'unica autenticazione disponibile, per esempio vogliamo supportare anche JWT o avere degli endpoint pubblici. Se utilizzassimo RequireCertificate, invece, la richiesta in assenza di certificato verrebbe rigettata a livello di web server, prima cioè che raggiunga la nostra applicazione.

Successivamente, carichiamo in memoria il certificato a partire dal file che abbiamo generato per recuperarne la Thumbprint, e aggiungiamo uno schema di autenticazione all'interno del quale abbiamo inserito un controllo sulla Thumbprint ricevuta, per verificare che sia la stessa che ci aspettiamo.

Come ultimo passo, ovviamente, dovremo anche aggiungere i servizi di Authorization, così da poter impostare i controller come protetti, oltre che i relativi middleware.

Se abbiamo effettuato tutti i passaggi correttamente, non ci resta che marcare il nostro WeatherForecastController con l'attributo Authorize, e provare a eseguire l'applicazione.

[ApiController]
[Authorize]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
   ...
}

All'apertura del browser, ci verrà richiesto di selezionare un certificate da utilizzare per il client:


Come possiamo facilmente verificare, solo utilizzando quello denominato CertDemoClient avremo effettivamente accesso all'endpoint.

Per rimuovere i certificati generati e tornare a una situazione "pulita" sul nostro sistema, possiamo utilizzare ancora PowerShell:

Get-ChildItem -Path "cert:\CurrentUser\Root\$rootThumbprint" | Remove-Item
Get-ChildItem -Path "cert:\CurrentUser\My\$clientThumbprint" | Remove-Item

Un'ultima nota riguarda il fatto che questo codice è da intendersi per sole finalità di test e sviluppo, ma non è adatto per uno scenario di produzione: tipicamente, in questi casi, non utilizzeremo un Self Signed certificate, ma uno creato da una Trusted Authority, che installeremo verosimilmente in uno storage sicuro come KeyVault. Pertanto il codice per recuperarne la thumbprint e per configurare l'autenticazione sarà leggermente diverso. Ce ne occuperemo in un prossimo script.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi