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
Controllare gli accessi IP alle app con Azure Container Apps
Autenticarsi in modo sicuro su Azure tramite GitHub Actions
Utilizzare gli snapshot con Azure File shares
Configurare policy CORS in Azure Container Apps
Evitare la command injection in un workflow di GitHub
Sfruttare lo stream rendering per le pagine statiche di Blazor 8
Gestire undefined e partial nelle reactive forms di Angular
Usare le collection expression per inizializzare una lista di oggetti in C#
Creazione di plugin per Tailwind CSS: espandere le funzionalità del framework dinamicamente
Utilizzare le Cache API di JavaScript per salvare elementi nella cache del browser
Utilizzare domini personalizzati gestiti automaticamente con Azure Container Apps
Utilizzare i nuovi piani dedicati di Azure Container Apps