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
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Code scanning e advanced security con Azure DevOps
Evitare (o ridurre) il repo-jacking sulle GitHub Actions
Le novità di Angular: i miglioramenti alla CLI
Usare le navigation property in QuickGrid di Blazor
Creare una libreria CSS universale: i bottoni
Utilizzare Tailwind CSS all'interno di React: installazione
Effettuare il binding di date in Blazor
Utilizzare QuickGrid di Blazor con Entity Framework
Utilizzare Tailwind CSS all'interno di React: primi componenti
Sfruttare lo stream rendering per le pagine statiche di Blazor 8
Routing statico e PreRendering in una Blazor Web App
Migliorare la sicurezza dei prompt con Azure AI Studio