All Projects → italia → spid-aspnetcore

italia / spid-aspnetcore

Licence: MIT license
AspNetCore Remote Authenticator for SPID

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to spid-aspnetcore

spid-sp-test
SAML2 SPID/CIE Service Provider validation tool
Stars: ✭ 27 (-3.57%)
Mutual labels:  saml2, spid
Abp.Castle.NLog
Abp的NLog日志输出模块。
Stars: ✭ 15 (-46.43%)
Mutual labels:  aspnetcore
AspNetCore.Identity.DynamoDB
DynamoDB Data Store Adaptor for ASP.NET Core Identity
Stars: ✭ 31 (+10.71%)
Mutual labels:  aspnetcore
smart-blazor
Blazor UI Components & Examples
Stars: ✭ 32 (+14.29%)
Mutual labels:  aspnetcore
MonolithicArchitecture
This repository presents an approach on how to build an application using Monolithic architecture, ASP.NET Core, EntityFrameworkCore, Identity Server, CQRS, DDD
Stars: ✭ 18 (-35.71%)
Mutual labels:  aspnetcore
KissLog.Sdk
KissLog is a lightweight and highly customizable logging framework for .NET applications
Stars: ✭ 33 (+17.86%)
Mutual labels:  aspnetcore
run-aspnet-identityserver4
Secure microservices with using standalone Identity Server 4 and backing with Ocelot API Gateway. Protect our ASP.NET Web MVC and API applications with using OAuth 2 and OpenID Connect in IdentityServer4. Securing your web application and API with tokens, working with claims, authentication and authorization middlewares and applying policies.
Stars: ✭ 159 (+467.86%)
Mutual labels:  aspnetcore
Ext.NET
Ext.NET public Issues.
Stars: ✭ 28 (+0%)
Mutual labels:  aspnetcore
http-exceptions
Return exceptions over HTTP e.g. as ASP.NET Core Problem Details, and HTTP-specific exception classes that enable ASP.NET to generate exception information
Stars: ✭ 42 (+50%)
Mutual labels:  aspnetcore
aspdotnet-core-fundamentals
Persian notes for ASP.NET Core Fundamentals course (Pluralsight)
Stars: ✭ 25 (-10.71%)
Mutual labels:  aspnetcore
aspnet-core-react-redux-playground-template
SPA template built with ASP.NET Core 6.0 + React + Redux + TypeScript + Hot Module Replacement (HMR)
Stars: ✭ 78 (+178.57%)
Mutual labels:  aspnetcore
ZooKeeper-Admin
ZooKeeper 管理工具
Stars: ✭ 45 (+60.71%)
Mutual labels:  aspnetcore
MiCake
🍰一款基于.Net Core平台的“超轻柔“领域驱动设计(DDD)组件
Stars: ✭ 112 (+300%)
Mutual labels:  aspnetcore
DNZ.MvcComponents
A set of useful UI-Components (HtmlHelper) for ASP.NET Core MVC based-on Popular JavaScript Plugins (Experimental project).
Stars: ✭ 25 (-10.71%)
Mutual labels:  aspnetcore
dotNetify-react-native-demo
DotNetify + React Native + .NET Core demo
Stars: ✭ 43 (+53.57%)
Mutual labels:  aspnetcore
BlazorAuthenticationSample
A sample showing some of the ASP.NET Core Blazor authentication features (also some testing...) 🚀
Stars: ✭ 78 (+178.57%)
Mutual labels:  aspnetcore
create-dotnet-devcert
A simple script that creates and trusts a self-signed development certificate for dotnet on Linux distributions.
Stars: ✭ 149 (+432.14%)
Mutual labels:  aspnetcore
shib-cas-authn3
Integrates an external CAS Server and Shibboleth IdPv3.
Stars: ✭ 21 (-25%)
Mutual labels:  saml2
webpack-aspnetcore
🚀 Make Webpack and ASP.NET Core friends easy way
Stars: ✭ 28 (+0%)
Mutual labels:  aspnetcore
run-aspnetcore-blazor
New .Net Core 3.0 Asp.Net Blazor Components SPA Web Application
Stars: ✭ 22 (-21.43%)
Mutual labels:  aspnetcore

AspNetCore Remote Authenticator for SPID

This is a custom implementation of an AspNetCore RemoteAuthenticationHandler for SPID (a.k.a. the Italian 'Sistema Pubblico di Identità Digitale'). Since it's an Italian-only thing, there's no point in struggling with an english README, just italian from now on.

Lo scopo di questo progetto è quello di fornire uno strumento semplice ed immediato per integrare, in una WebApp sviluppata con AspNetCore MVC, i servizi di autenticazione di SPID, automatizzando i flussi di login/logout, la gestione del protocollo SAML, la security e semplificando le attività di sviluppo e markup. All'interno del repository è presente sia il codice della libreria (SPID.AspNetCore.Authentication), che una web app demo (SPID.AspNetCore.WebApp) che mostra una integrazione di esempio della libreria all'interno di un'app AspNetCore ed è utilizzata anche nell'action di CI per la validazione dei test di compliance.

Integrazione

La libreria viene distribuita sotto forma di pacchetto NuGet, installabile tramite il comando

Install-Package SPID.AspNetCore.Authentication

A questo punto è sufficiente, all'interno dello Startup.cs, aggiungere le seguenti righe:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services
        .AddAuthentication(o => {
            o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = SpidDefaults.AuthenticationScheme;
        })
        .AddSpid(Configuration, o => {
            o.LoadFromConfiguration(Configuration);
        })
        .AddCookie();
}

In questo modo vengono aggiunti i middleware necessari per la gestione delle richieste/risposte di login/logout da/verso SPID. Tali middleware aggiungono alla webapp gli endpoint /signin-spid e /signout-spid sui quali la libreria è in ascolto per interpretare le risposte rispettivamente di Login e Logout provenienti dagli IdentityProvider di spid. Tali endpoint, nella loro URL assoluta, e quindi comprensivi di schema e hostname (ad esempio https://webapp.customdomain.it/signin-spid e https://webapp.customdomain.it/signout-spid), devono essere indicati rispettivamente nei tag AssertionConsumerService e SingleLogoutService del metadata del SP.

Nella libreria è inclusa anche l'implementazione di un TagHelper per la renderizzazione (conforme alle specifiche) del pulsante "Entra con SPID". Per renderizzare il pulsante è sufficiente aggiungere il seguente codice alla View Razor dove lo si desidera posizionare:

@using SPID.AspNetCore.Authentication
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, SPID.AspNetCore.Authentication
@{
	ViewData["Title"] = "Login Page";
}
@section styles {
	<style spid></style>
}
<div class="text-center">
	<h1 class="display-4">Welcome</h1>
	<spid-providers challenge-url="/home/login" size="Medium" class="text-left"></spid-providers>
</div>

@section scripts {
	<script spid></script>
}

Il TagHelper spid-providers si occuperà di generare automaticamente il codice HTML necessario per la renderizzazione della lista di IdentityProviders che è stata inizializzata tra le SpidOptions in fase di startup. L'attributo size può essere valorizzato con i valori Small, Medium, Large, ExtraLarge. <style spid></style> e <script spid></script> invece rappresentano i TagHelper per la renderizzazione rispettivamente delle classi CSS e del codice JS necessari all'esecuzione del pulsante. Un esempio completo di webapp AspNetCore MVC che fa uso di questa libreria è presente all'interno di questo repository sotto la cartella SPID.AspNetCore.Authentication/SPID.AspNetCore.WebApp. Per utilizzarla è sufficiente configurare in appsettings.json i parametri AssertionConsumerServiceIndex, AttributeConsumingServiceIndex, EntityId e Certificate con quelli relativi al proprio metadata di test, e lanciare la webapp.

Configurazione

E' possibile configurare la libreria leggendo le impostazioni da Configurazione, tramite il comando

o.LoadFromConfiguration(Configuration);

In particolare è possibile aggiungere alla configurazione una sezione 'Spid' che ha il seguente formato

  "Spid": {
    "Providers": [
      {
        "Name": "Validator",
        "OrganizationName": "Validator SPID",
        "OrganizationDisplayName": "Validator SPID",
        "OrganizationUrlMetadata": "https://validator.spid.gov.it/metadata.xml",
        "OrganizationUrl": "https://validator.spid.gov.it",
        "OrganizationLogoUrl": "https://validator.spid.gov.it/img/idp-logo.png",
        "SingleSignOnServiceUrl": "https://validator.spid.gov.it/samlsso",
        "SingleSignOutServiceUrl": "https://validator.spid.gov.it/samlsso",
        "Method": "Post",
        "Type": "StagingProvider"
      },
      {
        "Name": "SpidSpTest",
        "OrganizationName": "SpidSpTest",
        "OrganizationDisplayName": "SpidSpTest",
        "OrganizationUrlMetadata": "https://localhost:5001/spid/idp_metadata.xml",
        "OrganizationUrl": "https://github.com/italia/spid-testenv-docker",
        "OrganizationLogoUrl": "https://validator.spid.gov.it/img/idp-logo.png",
        "SingleSignOnServiceUrl": "https://localhost:8080/samlsso",
        "SingleSignOutServiceUrl": "https://localhost:8080/samlsso",
        "Method": "Post",
        "Type": "DevelopmentProvider"
      },
      {
        "Name": "DemoSpid",
        "OrganizationName": "DemoSpid",
        "OrganizationDisplayName": "DemoSpid",
        "OrganizationUrlMetadata": "https://demo.spid.gov.it/validator/metadata.xml",
        "OrganizationUrl": "https://demo.spid.gov.it",
        "OrganizationLogoUrl": "https://validator.spid.gov.it/img/idp-logo.png",
        "SingleSignOnServiceUrl": "https://demo.spid.gov.it/validator/samlsso",
        "SingleSignOutServiceUrl": "https://demo.spid.gov.it/validator/samlsso",
        "Method": "Post",
        "Type": "DevelopmentProvider"
      },
      {
        "Name": "Aruba",
        "OrganizationName": "ArubaPEC S.p.A.",
        "OrganizationDisplayName": "ArubaPEC S.p.A.",
        "OrganizationUrlMetadata": "https://loginspid.aruba.it/metadata",
        "OrganizationUrl": "https://www.pec.it/",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-arubaid.png",
        "SingleSignOnServiceUrl": "https://loginspid.aruba.it/ServiceLoginWelcome",
        "SingleSignOutServiceUrl": "https://loginspid.aruba.it/ServiceLogoutRequest",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Poste",
        "OrganizationName": "Poste Italiane SpA",
        "OrganizationDisplayName": "Poste Italiane SpA",
        "OrganizationUrlMetadata": "https://posteid.poste.it/jod-fs/metadata/metadata.xml",
        "OrganizationUrl": "https://www.poste.it/",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-posteid.png",
        "SingleSignOnServiceUrl": "https://posteid.poste.it/jod-fs/ssoservicepost",
        "SingleSignOutServiceUrl": "https://posteid.poste.it/jod-fs/sloservicepost",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Intesa",
        "OrganizationName": "IN.TE.S.A. S.p.A.",
        "OrganizationDisplayName": "IN.TE.S.A. S.p.A.",
        "OrganizationUrlMetadata": "https://spid.intesa.it/metadata/metadata.xml",
        "OrganizationUrl": "https://www.intesa.it/",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-intesaid.png",
        "SingleSignOnServiceUrl": "https://spid.intesa.it/Time4UserServices/services/idp/AuthnRequest/",
        "SingleSignOutServiceUrl": "https://spid.intesa.it/Time4UserServices/services/idp/SingleLogout",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Infocert",
        "OrganizationName": "InfoCert S.p.A.",
        "OrganizationDisplayName": "InfoCert S.p.A.",
        "OrganizationUrlMetadata": "https://identity.infocert.it/metadata/metadata.xml",
        "OrganizationUrl": "https://www.infocert.it",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-infocertid.png",
        "SingleSignOnServiceUrl": "https://identity.infocert.it/spid/samlsso",
        "SingleSignOutServiceUrl": "https://identity.infocert.it/spid/samlslo",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Lepida",
        "OrganizationName": "Lepida S.p.A.",
        "OrganizationDisplayName": "Lepida S.p.A.",
        "OrganizationUrlMetadata": "https://id.lepida.it/idp/shibboleth",
        "OrganizationUrl": "https://www.lepida.it",
        "OrganizationLogoUrl": "https://id.lepida.it/idm/app/pubblica/lepida_spid.png",
        "SingleSignOnServiceUrl": "https://id.lepida.it/idp/profile/SAML2/POST/SSO",
        "SingleSignOutServiceUrl": "https://id.lepida.it/idp/profile/SAML2/POST/SLO",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Namirial",
        "OrganizationName": "Namirial S.p.a.",
        "OrganizationDisplayName": "Namirial S.p.a.",
        "OrganizationUrlMetadata": "https://idp.namirialtsp.com/idp/metadata",
        "OrganizationUrl": "https://www.namirialtsp.com",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-namirialid.png",
        "SingleSignOnServiceUrl": "https://idp.namirialtsp.com/idp/profile/SAML2/POST/SSO",
        "SingleSignOutServiceUrl": "https://idp.namirialtsp.com/idp/profile/SAML2/POST/SLO",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Register",
        "OrganizationName": "Register.it S.p.A.",
        "OrganizationDisplayName": "Register.it S.p.A.",
        "OrganizationUrlMetadata": "https://spid.register.it/login/metadata",
        "OrganizationUrl": "https//www.register.it",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-spiditalia.png",
        "SingleSignOnServiceUrl": "https://spid.register.it/login/sso",
        "SingleSignOutServiceUrl": "https://spid.register.it/login/singleLogout",
        "Method": "Post",
        "Type": "IdentityProvider"
      },
      {
        "Name": "Sielte",
        "OrganizationName": "Sielte S.p.A.",
        "OrganizationDisplayName": "Sielte S.p.A.",
        "OrganizationUrlMetadata": "https://identity.sieltecloud.it/simplesaml/metadata.xml",
        "OrganizationUrl": "http://www.sielte.it",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-sielteid.png",
        "SingleSignOnServiceUrl": "https://identity.sieltecloud.it/simplesaml/saml2/idp/SSO.php",
        "SingleSignOutServiceUrl": "https://identity.sieltecloud.it/simplesaml/saml2/idp/SLO.php",
        "Method": "Post",
        "Type": "IdentityProvider",
        "NowDelta": -2
      },
      {
        "Name": "Tim",
        "OrganizationName": "Trust Technologies srl",
        "OrganizationDisplayName": "Trust Technologies srl",
        "OrganizationUrlMetadata": "https://login.id.tim.it/spid-services/MetadataBrowser/idp",
        "OrganizationUrl": "https://www.trusttechnologies.it",
        "OrganizationLogoUrl": "https://raw.githubusercontent.com/italia/spid-graphics/master/idp-logos/spid-idp-timid.png",
        "SingleSignOnServiceUrl": "https://login.id.tim.it/affwebservices/public/saml2sso",
        "SingleSignOutServiceUrl": "https://login.id.tim.it/affwebservices/public/saml2slo",
        "Method": "Post",
        "Type": "IdentityProvider",
        "NowDelta": -2
      }
    ],
    "Certificate": {
      "Source": "Store/File/Raw/None",
      "Store": {
        "Location": "CurrentUser",
        "Name": "My",
        "FindType": "FindBySubjectName",
        "FindValue": "certificatesubjectname",
        "validOnly": false
      },
      "File": {
        "Path": "certificatefilename.pfx",
        "Password": "password" 
      },
      "Raw": {
        "Certificate": "base64rawcertificate-exportedwithprivatekey",
        "Password": "password"
      }
    },
    "IsLocalValidatorEnabled": false,
    "IsStagingValidatorEnabled": true,
    "EntityId": "https://entityID",
    "AssertionConsumerServiceURL": "https://localhost:5001/signin-spid",
    "AssertionConsumerServiceIndex": 0,
    "AttributeConsumingServiceIndex": 0,
    "RandomIdentityProvidersOrder": false
  }

La configurazione del certificato del SP avviene specificando nel campo Source uno tra i valori Store/File/Raw/None (nel caso di None non verrà caricato un certificato durante lo startup, ma sarà necessario fornirne uno a runtime, tramite l'uso dei CustomSpidEvents, che verranno presentati più nel dettaglio nella sezione successiva) e compilando opportunamente la sezione corrispondente al valore specificato. Le sezioni non usate (quelle cioè corrispondenti agli altri valori) potranno essere tranquillamente eliminate dal file di configurazione, dal momento che non verranno lette.

In alternativa, è possibile configurare tutte le suddette opzioni programmaticamente, dal metodo AddSpid(options => ...). Gli endpoint di callback per le attività di signin e signout sono impostati di default, rispettivamente, a /signin-spid e /signout-spid (che, sotto forma di URL assoluta, e quindi comprensivi di schema e hostname, devono essere indicati rispettivamente nei tag AssertionConsumerService e SingleLogoutService del metadata del SP), ma laddove fosse necessario modificare queste impostazioni, è possibile sovrascriverle (sia da configurazione che da codice) reimpostando le options CallbackPath e RemoteSignOutPath. I valori di AssertionConsumerServiceIndex e AssertionConsumerServiceURL sono mutuamente esclusivi, è possibile indicare l'uno o l'altro, ma l'indicazione di entrambi causa la restituzione del codice di errore n.16 da parte dell'IdentityProvider.

Punti d'estensione

E' possibile intercettare le varie fasi di esecuzione del RemoteAuthenticator, effettuando l'override degli eventi esposti dalla option Events, ed eventualmente utilizzare la DependencyInjection per avere a disposizione i vari servizi configurati nella webapp. Questo torna utile sia in fase di inspection delle request e delle response da/verso SPID, sia per personalizzare, a runtime, alcuni parametri per la generazione della richiesta SAML (ad esempio nel caso in cui si voglia implementare la multitenancy). Ad esempio

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services
        .AddAuthentication(o => {
            o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            o.DefaultChallengeScheme = SpidDefaults.AuthenticationScheme;
        })
        .AddSpid(Configuration, o => {
            o.Events.OnTokenCreating = async (s) => await s.HttpContext.RequestServices.GetRequiredService<CustomSpidEvents>().TokenCreating(s);
            o.Events.OnAuthenticationSuccess = async (s) => await s.HttpContext.RequestServices.GetRequiredService<CustomSpidEvents>().AuthenticationSuccess(s);
            o.LoadFromConfiguration(Configuration);
        })
        .AddCookie();
    services.AddScoped<CustomSpidEvents>();
}

.....

public class CustomSpidEvents : SpidEvents
{
    private readonly IMyService _myService;
    public CustomSpidEvents(IMyService myService)
    {
        _myService = myService;
    }

    public override Task TokenCreating(SecurityTokenCreatingContext context)
    {
        var customConfig = _myService.ReadMyCustomConfigFromWhereverYouWant();
        context.TokenOptions.EntityId = customConfig.EntityId;
        context.TokenOptions.AssertionConsumerServiceIndex = customConfig.AssertionConsumerServiceIndex;
        context.TokenOptions.AttributeConsumingServiceIndex = customConfig.AttributeConsumingServiceIndex;
        context.TokenOptions.Certificate = customConfig.Certificate;

        return base.TokenCreating(context);
    }
    
    public override Task AuthenticationSuccess(AuthenticationSuccessContext context)
    {
        var principal = context.AuthenticationTicket.Principal;
	
	// Recupero dati provenienti da Spid da ClaimsPrincipal
        var spidCode = principal.FindFirst(SpidClaimTypes.SpidCode);
        var name = principal.FindFirst(SpidClaimTypes.Name);
        var surname = principal.FindFirst(SpidClaimTypes.FamilyName);
        var email = principal.FindFirst(SpidClaimTypes.Email);
        var fiscalCode = principal.FindFirst(SpidClaimTypes.FiscalNumber);
        // ............etc........
	
        return base.AuthenticationSuccess(context);
    }

}

Inoltre è possibile usare gli SpidEvents per implementare il log obbligatorio delle request e delle response SAML, come previsto dalle regole tecniche e dalla convenzione. Di seguito un esempio che fa uso di uno storage esterno per conservare i log serializzati xml delle request e delle response SAML:

private static XmlSerializer loginRequestSerializer = new XmlSerializer(typeof(AuthnRequestType));
private static XmlSerializer logoutRequestSerializer = new XmlSerializer(typeof(LogoutRequestType));
public override async Task RedirectToIdentityProvider(RedirectContext context)
{
    using MemoryStream stream = new MemoryStream();
    string id = string.Empty;
    if (context.SignedProtocolMessage is AuthnRequestType loginRequest)
    {
        id = loginRequest.ID;
        loginRequestSerializer.Serialize(stream, loginRequest);
    }
    else if (context.SignedProtocolMessage is LogoutRequestType logoutRequest)
    {
        id = logoutRequest.ID;
        logoutRequestSerializer.Serialize(stream, logoutRequest);
    }

    stream.Position = 0;

    await _fileStorage.UploadFileAsync("spid", $"{id}_Request", stream, "application/xml", $"{id}_Request");

    await base.RedirectToIdentityProvider(context);
}

private static XmlSerializer loginResponseSerializer = new XmlSerializer(typeof(ResponseType));
public override async Task MessageReceived(MessageReceivedContext context)
{
    var id = context.ProtocolMessage.InResponseTo;
    using MemoryStream stream = new MemoryStream();
    loginResponseSerializer.Serialize(stream, context.ProtocolMessage);

    stream.Position = 0;

    await _fileStorage.UploadFileAsync("spid", $"{id}_Response", stream, "application/xml", $"{id}_Response");

    await base.MessageReceived(context);
}

private static XmlSerializer logoutResponseSerializer = new XmlSerializer(typeof(LogoutResponseType));
public override async Task RemoteSignOut(RemoteSignOutContext context)
{
    var id = context.ProtocolMessage.InResponseTo;
    using MemoryStream stream = new MemoryStream();
    logoutResponseSerializer.Serialize(stream, context.ProtocolMessage);

    stream.Position = 0;

    await _fileStorage.UploadFileAsync("spid", $"{id}_Response", stream, "application/xml", $"{id}_Response");

    await base.RemoteSignOut(context);
}

Generazione Metadata Service Provider

La libreria è dotata della possibilità di generare dinamicamente dei metadata per Service Provider conformi con i seguenti profili:

  • spid-sp-public: Public Spid SP
  • spid-sp-private: Private Spid SP
  • spid-sp-ag-public-full: Public Spid SP Aggregatore Full
  • spid-sp-ag-public-lite: Public Spid SP Aggregatore Lite
  • spid-sp-op-public-full: Public Spid SP Gestore Full
  • spid-sp-op-public-lite: Public Spid SP Gestore Lite

E' possibile aggiungere nuovi ServiceProvider sia in maniera procedurale, in fase di Startup, come segue:

.AddSpid(o =>
{
    o.LoadFromConfiguration(Configuration);
    o.ServiceProviders.AddRange(GetServiceProviders(o));
})

......

private List<Authentication.Models.ServiceProviders.ServiceProvider> GetServiceProviders(SpidOptions o)
{
    return new List<Authentication.Models.ServiceProviders.ServiceProvider>(){
	    new Authentication.Models.ServiceProviders.ServiceProviderPublic()
	    {
		FileName = "metadata.xml",
		Certificate = o.Certificate,
		Id = Guid.NewGuid(), // Questa impostazione è solo di esempio, per i metadata reali è necessario mantenere sempre lo stesso Id
		EntityId = "https://spid.asfweb.it/",
		SingleLogoutServiceLocations = new List<SingleLogoutService>() {
		    new SingleLogoutService() {
			Location = "https://localhost:5001/signout-spid",
			ProtocolBinding = ProtocolBinding.POST
		    }
		    ..... // 1 o più
		},
		AssertionConsumerServices = new System.Collections.Generic.List<AssertionConsumerService>() {
		    new AssertionConsumerService() {
			Index = 0,
			IsDefault = true,
			Location = "https://localhost:5001/signin-spid",
			ProtocolBinding = ProtocolBinding.POST
		    }
		    ..... // 1 o più
		},
		AttributeConsumingServices = new System.Collections.Generic.List<AttributeConsumingService>() {
		    new AttributeConsumingService() {
			Index = 0,
			ServiceName = "Service 1",
			ServiceDescription = "Service 1",
			ClaimTypes = new SpidClaimTypes[] {
			    SpidClaimTypes.Name,
			    SpidClaimTypes.FamilyName,
			    SpidClaimTypes.FiscalNumber,
			    SpidClaimTypes.Email
			    ..........
			}
		    },
		    ..... // 1 o più
		},
		OrganizationName = "Organizzazione fittizia per il collaudo",
		OrganizationDisplayName = "Organizzazione fittizia per il collaudo",
		OrganizationURL = "https://www.asfweb.it/",
		VatNumber = "IT01261280620",
		EmailAddress = "[email protected]",
		TelephoneNumber = "+3908241748276",
		IPACode = "__aggrsint"
	    },
.......

sia utilizzando una classe che implementa l'interfaccia IServiceProvidersFactory e configurandola come segue:

.AddSpid(o =>
{
    o.LoadFromConfiguration(Configuration);
})
.AddServiceProvidersFactory<ServiceProvidersFactory>();

........

public class ServiceProvidersFactory : IServiceProvidersFactory
{
	public Task<List<ServiceProvider>> GetServiceProviders()
	    => Task.FromResult(new List<ServiceProvider>() {
		new Authentication.Models.ServiceProviders.ServiceProviderPublicFullAggregator()
		{
..............

Infine, per poter esporre gli endpoint dei metadata relativi ai Service Provider registrati, sarà necessario aggiungere la seguente riga:

app.AddSpidSPMetadataEndpoints();

Tutti i metadata generati vengono automaticamente esposti su endpoint diversi, che hanno come BasePath /metadata-spid (ad esempio, un metadata definito con NomeFile = metadata.xml verrà esposto sull'endpoint /metadata-spid/metadata.xml): il BasePath può essere cambiato, sovrascrivendo la proprietà ServiceProvidersMetadataEndpointsBasePath sulle SpidOptions nello Startup.cs.

All'interno dell'esempio 1_SimpleSPWebApp è presente un ServiceProvider di esempio per ogni tipologia di profilo, sia configurato in maniera procedurale, sia tramite IServiceProvidersFactory.

Error Handling

La libreria può, in qualunque fase (sia in fase di creazione della Request sia in fase di gestione della Response), sollevare eccezioni. Un tipico scenario è quello in cui vengono ricevuti i codici di errore previsti dal protocollo SPID (n.19, n.20, ecc....), in tal caso la libreria solleva un'eccezione contenente il corrispondente messaggio d'errore localizzato, richiesto dalle specifiche SPID, che è possibile gestire (ad esempio per la visualizzazione) utilizzando il normale flusso previsto per AspNetCore. L'esempio seguente fa uso del middleware di ExceptionHandling di AspNetCore.

public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
    ...
    app.UseExceptionHandler("/Home/Error");
    ...
}

.......

// HomeController
[AllowAnonymous]
public async Task<IActionResult> Error()
{
    var exceptionHandlerPathFeature =
        HttpContext.Features.Get<IExceptionHandlerPathFeature>();

    string errorMessage = string.Empty;

    if (exceptionHandlerPathFeature?.Error != null)
    {
        var messages = FromHierarchy(exceptionHandlerPathFeature?.Error, ex => ex.InnerException)
            .Select(ex => ex.Message)
            .ToList();
        errorMessage = String.Join(" ", messages);
    }

    return View(new ErrorViewModel
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier,
        Message = errorMessage
    });
}

private IEnumerable<TSource> FromHierarchy<TSource>(TSource source,
            Func<TSource, TSource> nextItem,
            Func<TSource, bool> canContinue)
{
    for (var current = source; canContinue(current); current = nextItem(current))
    {
        yield return current;
    }
}

private IEnumerable<TSource> FromHierarchy<TSource>(TSource source,
    Func<TSource, TSource> nextItem)
    where TSource : class
{
    return FromHierarchy(source, nextItem, s => s != null);
}

eIDAS

Dalla versione 1.3.0 in poi la libreria supporta anche la login con eIDAS. Per utilizzare tale modalità è sufficiente aggiungere la sezione "Eidas" all'interno del file di configurazione, come segue:

  "Spid": {
  ......
  },
  "Eidas": {
    "Name": "Eidas",
    "OrganizationName": "eIDAS Test/PreProduzione",
    "OrganizationDisplayName": "eIDAS Test/PreProduzione",
    "OrganizationUrlMetadata": "https://sp-proxy.pre.eid.gov.it/spproxy/idpitmetadata",
    "OrganizationUrl": "https://www.eid.gov.it/",
    "OrganizationLogoUrl": "https://www.eid.gov.it/assets/img/logo-eIDAS-login.svg",
    "SingleSignOnServiceUrl": "https://sp-proxy.pre.eid.gov.it/spproxy/samlsso",
    "SingleSignOutServiceUrl": "https://sp-proxy.pre.eid.gov.it/spproxy/samlslo",
    "Method": "Post",
    "SecurityLevel": 2,
    "AttributeConsumingServiceIndex": 99 // Or 100
  }

All'interno della configurazione dell'IdentityProvider è possibile specificare il valore di AttributeConsumingServiceIndex (99 o 100, come riportato nei metadata) da utilizzare per costruire la request, valore che sovrascrive (per eIDAS) il valore di default specificato nella sezione Spid. Per renderizzare il pulsante "Login with eIDAS" è sufficiente aggiungere il seguente codice alla view Razor.

@using SPID.AspNetCore.Authentication
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, SPID.AspNetCore.Authentication
@{
	ViewData["Title"] = "Login Page";
}
@section styles {
	<style eidas></style>
}
<div class="text-center">
	<h1 class="display-4">Welcome</h1>
	<eidas-button challenge-url="/home/login" size="Medium" circle-image-type="ywb" class="text-left"></eidas-button>
</div>

Il tag eidas-button prevede, oltre agli attributi già definiti per il pulsante SPID (come size e challenge-url), un attributo circle-image-type, che definisce le diverse tipologie di pulsanti eIDAS che è possibile renderizzare, e i valori che può assumere sono db, lb, ybw, ywb.

Compatibilità con Bootstrap

Se la WebApp utilizza Bootstrap, è necessario aggiungere la seguente classe al fine di visualizzare correttamente il pulsante "Entra con SPID"

.spid-idp-button * {
  box-sizing: content-box;
}

Esempi

All'interno della cartella samples è possibile trovare alcune implementazioni esemplificative di webapp che fanno uso della libreria:

  • 1_SimpleSPWebApp: semplice webapp AspNetCore MVC che utilizza Spid come sistema di login esterno
  • 2_IdentityServer: implementazione di una istanza di IdentityServer4 (che fa da IAM proxy OIDC verso Spid) che utilizza Spid come sistema di login esterna, e una webapp MVC federata con l'istanza di IdentityServer4
  • 3_RazorPages: semplice webapp AspNetCore con RazorPages che utilizza Spid come sistema di login esterno

Questi esempi sono solo esemplificativi dell'integrazione con la libreria, non devono essere utilizzati "as-is" in ambienti di produzione.

Compliance

La libreria è stata oggetto di collaudo da parte di AGID, sia per soluzioni come ServiceProvider che come Aggregatore, ha superato tutti i test di spid-sp-test (che è integrata in CI, è possibile vedere i log nelle actions), ed è compliant con le direttive specificate negli avvisi SPID.

Authors

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].