Nello sviluppo di una applicazione ASP.NET MVC è possibile che prima o poi ci si trovi nella necessità di restringere l'accesso ad una o più risorse web. Se la nostra applicazione prevede un sistema di autenticazione di qualsivoglia tipo (come ad esempio ASP.NET Membership Provider o il più recente ASP.NET Identity) è possibile sfruttare il sistema di utenti e ruoli predefinito per restringere l'accesso alle risorse che vogliamo tramite l'utilizzo dell'attributo AuthorizeAttribute presente nel namespace System.Web.Mvc a livello di Controller o di singolo ActionResult:
1 2 3 4 5 |
[Authorize(Roles = "Administrators, Operators")] public class HomeController : BaseController { ... } |
1 2 3 4 5 6 7 8 |
public class HomeController : BaseController { [Authorize(Roles = "Administrators, Operators")] public ActionResult Index() { ... } } |
Questo approccio richiede però necessariamente che l'utente effettui un login ed è quindi poco indicato qualora si voglia mettere in piedi un WebService o una interfaccia RESTful di qualsivoglia tipo. Per queste situazioni specifiche è probabilmente più indicato uno dei meccanismi di autenticazione previsti dal protocollo HTTP: Basic, Digest, NTLM solo per citarne alcuni. Il più diffuso, nonché il più semplice da supportare in ambiente MVC, è senza dubbio il meccanismo di Basic Authentication, per implementare il quale è sufficiente aggiungere al proprio progetto il seguente attributo ActionFilter personalizzato:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class BasicAuthenticationAttribute : ActionFilterAttribute { public string BasicRealm { get; set; } protected string Username { get; set; } protected string Password { get; set; } public BasicAuthenticationAttribute(string username, string password) { this.Username = username; this.Password = password; } public override void OnActionExecuting(ActionExecutingContext filterContext) { var req = filterContext.HttpContext.Request; var auth = req.Headers["Authorization"]; if (!String.IsNullOrEmpty(auth)) { var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':'); var user = new { Name = cred[0], Pass = cred[1] }; if (user.Name == Username && user.Pass == Password) return; } var res = filterContext.HttpContext.Response; res.StatusCode = 401; res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel")); res.End(); } } |
Come si può vedere il filtro controlla la presenza del campo Authorization negli header della Http Request e agisce di conseguenza:
- Se presente, confronta l'username e la password presenti nella richiesta con quelle previste dal sistema.
- Se non presente, produce un Http Response di tipo 401 per avvisare il client che per accedere alla risorsa richiesta è necessario l'inserimento di apposite credenziali. Questo tipo di risposta produrrà, nella maggior parte dei browser web, la classica finestra pop-up di inserimento di username e password.
L'implementazione mostrata prevede delle credenziali inserite ad-hoc, ma può essere agevolmente estesa per far sì che l'ActionFilter utilizzi un qualsivoglia sistema di autenticazione personalizzato (MembershipProvider, ASP.NET Identity, base utenti personalizzata su DB esterno o su file, etc.) semplicemente eliminando il costruttore personalizzato e modificando il contenuto del blocco IF presente nell'override del metodo OnActionExecuting.
Il principale vantaggio di gestire la Basic Authentication in questo modo è che l' ActionFilter così creato può essere utilizzato sia a livello di Controller che di singolo ActionResult, proprio come l'attributo Authorize menzionato sopra:
1 2 3 4 5 |
[BasicAuthenticationAttribute("your-username", "your-password", BasicRealm = "your-realm")] public class HomeController : BaseController { ... } |
1 2 3 4 5 6 7 8 |
public class HomeController : BaseController { [BasicAuthenticationAttribute("your-username", "your-password", BasicRealm = "your-realm")] public ActionResult Index() { ... } } |