I have an existing, working ASP.NET MVC 4 web application. I have written my own RoleProvider
and I am using the standard [Authorize] attribute. My controllers look like this:
[Authorize(Roles="ContactAdmins")] //System.Web.Mvc
public ActionResult Index()
I would like to add a WebAPI controller to my application, and take advantage of my existing plumbing
[Authorize(Roles="ContactAdmins")] //System.Web.Http
public IEnumerable<Contact> Get()
This works for Javascript ajax calls from within my site (since the browser user is already authenticated with a Forms auth cookie). My question is from a C# Console Application (or any other application that is not part of my web app) how can I authenticate to this API?
Lets assume that for the parts of my API which are public, I am using code very similar to what is found at this question Consuming WebApi in MVC3.
var url = "http://localhost:9000/api/contacts";
using (var client = new WebClient())
using (var reader = XmlReader.Create(client.OpenRead(url)))
{
var serializer = new XmlSerializer(typeof(Contact[]));
var contacts = (Contact[])serializer.Deserialize(reader);
// TODO: Do something with the contacts
}
What would I need to modify here? Or would I have to scrap this and use a completely different approach? I am not tied to using Forms for API Authentication of remote clients, but I would like to keep the current elegant approach for JavaScript clients that are part of the app (just request API since forms cookie is set).
You could combine the standard Forms Auth with a custom Basic Auth, based on the same primitives than Forms Auth. Note with Basic, HTTPS is strongly recommended (and in fact more and more Windows component do not support Basic+HTTP by default nowadays).
Here is a sample code for a Basic Authentication Module that reuses code from Forms Auth. It also comes with it's own configuration section (named 'basicAuth'). You want to make sure both auths (Forms and Basic) use the same cookie and parameters when configured together:
using System;
using System.ComponentModel;
using System.Configuration;
using System.Globalization;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
namespace MySecurity
{
public class BasicAuthenticationModule : IHttpModule
{
public event EventHandler<BasicAuthenticationEventArgs> Authenticate;
public void Dispose()
{
}
protected virtual string GetRealm(HttpContext context)
{
return BasicAuthenticationSection.Current.GetRealm(context);
}
public virtual void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
context.EndRequest += OnEndRequest;
}
protected virtual bool FormsAuthenticate(HttpContext context, string login, string password, string realm)
{
// check ad-hoc forms credentials, as we can support it even if forms auth is not configured
FormsAuthenticationConfiguration c = ((AuthenticationSection)ConfigurationManager.GetSection("system.web/authentication")).Forms;
if ((c.Credentials == null) || (c.Credentials.Users == null))
return false;
foreach (FormsAuthenticationUser user in c.Credentials.Users)
{
if ((string.Compare(user.Name, login, true, CultureInfo.CurrentCulture) == 0) &&
(string.Compare(user.Password, password, true, CultureInfo.CurrentCulture) == 0))
return true;
}
return false;
}
protected virtual bool OnAuthenticate(HttpContext context, string login, string password, string realm)
{
EventHandler<BasicAuthenticationEventArgs> handler = Authenticate;
if (handler != null)
{
BasicAuthenticationEventArgs e = new BasicAuthenticationEventArgs(context, login, password, realm);
handler(this, e);
return !e.Cancel;
}
return FormsAuthenticate(context, login, password, realm);
}
protected virtual string[] GetUserRoles(HttpContext context, string login, string realm)
{
// TODO: overwrite if needed
return new string[0];
}
protected virtual IPrincipal GetUser(HttpContext context, FormsAuthenticationTicket ticket)
{
return new GenericPrincipal(new BasicAuthenticationIdentity(ticket), GetUserRoles(context, ticket.Name, GetRealm(context)));
}
protected virtual void OnAuthenticated(HttpContext context)
{
}
protected virtual void OnEndRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if (application.Response.StatusCode != (int)HttpStatusCode.Unauthorized)
return;
string basic = "Basic Realm=\"" + GetRealm(application.Context) + "\"";
application.Response.AppendHeader("WWW-Authenticate", basic);
}
public static void SignOut()
{
if (HttpContext.Current == null)
return;
HttpContext.Current.Request.Cookies.Remove(BasicAuthenticationSection.Current.Name);
HttpContext.Current.Response.Cookies.Remove(BasicAuthenticationSection.Current.Name);
HttpCookie cookie = new HttpCookie(BasicAuthenticationSection.Current.Name);
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.Cookies.Add(cookie);
}
public static bool IsAuthenticated(HttpContext context)
{
if ((context == null) || (context.User == null) || (context.User.Identity == null))
return false;
return context.User.Identity.IsAuthenticated;
}
protected virtual void OnAuthenticateRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
if ((IsAuthenticated(application.Context)) && (!BasicAuthenticationSection.Current.ReAuthenticate))
return;
string encryptedTicket;
FormsAuthenticationTicket ticket;
HttpCookie cookie = application.Context.Request.Cookies[BasicAuthenticationSection.Current.Name];
if (cookie == null)
{
// no cookie, check auth header
string authHeader = application.Context.Request.Headers["Authorization"];
if ((string.IsNullOrEmpty(authHeader)) || (!authHeader.StartsWith("Basic ", StringComparison.InvariantCultureIgnoreCase)))
{
ResponseAccessDenied(application);
return;
}
string login;
string password;
string lp = authHeader.Substring(6).Trim();
if (string.IsNullOrEmpty(lp))
{
ResponseAccessDenied(application);
return;
}
lp = Encoding.Default.GetString(Convert.FromBase64String(lp));
if (string.IsNullOrEmpty(lp.Trim()))
{
ResponseAccessDenied(application);
return;
}
int pos = lp.IndexOf(':');
if (pos < 0)
{
login = lp;
password = string.Empty;
}
else
{
login = lp.Substring(0, pos).Trim();
password = lp.Substring(pos + 1).Trim();
}
if (!OnAuthenticate(application.Context, login, password, GetRealm(application.Context)))
{
ResponseAccessDenied(application);
return;
}
// send cookie back to client
ticket = new FormsAuthenticationTicket(login, false, (int)BasicAuthenticationSection.Current.Timeout.TotalMinutes);
encryptedTicket = FormsAuthentication.Encrypt(ticket);
cookie = new HttpCookie(BasicAuthenticationSection.Current.Name, encryptedTicket);
application.Context.Response.Cookies.Add(cookie);
// don't overwrite context user if it's been set
if ((!IsAuthenticated(application.Context)) || (BasicAuthenticationSection.Current.ReAuthenticate))
{
application.Context.User = GetUser(application.Context, ticket);
}
OnAuthenticated(application.Context);
application.Context.Response.StatusCode = (int)HttpStatusCode.OK;
return;
}
// there is a cookie, check it
encryptedTicket = cookie.Value;
if (string.IsNullOrEmpty(encryptedTicket))
{
ResponseAccessDenied(application);
return;
}
try
{
ticket = FormsAuthentication.Decrypt(encryptedTicket);
}
catch
{
ResponseAccessDenied(application);
return;
}
if (ticket.Expired)
{
ResponseAccessDenied(application);
return;
}
// set context user
// don't overwrite context user if it's been set
if ((!IsAuthenticated(application.Context) || (BasicAuthenticationSection.Current.ReAuthenticate)))
{
application.Context.User = GetUser(application.Context, ticket);
}
OnAuthenticated(application.Context);
}
protected virtual void WriteAccessDenied(HttpApplication application)
{
if (application == null)
throw new ArgumentNullException("application");
application.Context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
application.Context.Response.StatusDescription = "Unauthorized";
application.Context.Response.Write(application.Context.Response.StatusCode + " " + application.Context.Response.StatusDescription);
}
protected virtual void ResponseAccessDenied(HttpApplication application)
{
// if there is a bad cookie, kill it
application.Context.Request.Cookies.Remove(BasicAuthenticationSection.Current.Name);
application.Context.Response.Cookies.Remove(BasicAuthenticationSection.Current.Name);
HttpCookie cookie = new HttpCookie(BasicAuthenticationSection.Current.Name);
cookie.Expires = DateTime.Now.AddDays(-1);
HttpContext.Current.Response.Cookies.Add(cookie);
WriteAccessDenied(application);
application.CompleteRequest();
}
}
public class BasicAuthenticationSection : ConfigurationSection
{
public const string SectionName = "basicAuth";
private const string DefaultCookieName = "." + SectionName;
private static BasicAuthenticationSection _current;
public static BasicAuthenticationSection Current
{
get
{
return _current ?? (_current = ConfigurationManager.GetSection(SectionName) as BasicAuthenticationSection ?? new BasicAuthenticationSection());
}
}
[StringValidator(MinLength = 1), ConfigurationProperty("name", DefaultValue = DefaultCookieName)]
public string Name
{
get
{
return (string)base["name"];
}
}
internal string GetRealm(HttpContext context)
{
if (!string.IsNullOrEmpty(Realm))
return Realm;
return context.Request.Url.Host;
}
[ConfigurationProperty("realm", DefaultValue = "")]
public string Realm
{
get
{
return (string)base["realm"];
}
}
[ConfigurationProperty("domain", DefaultValue = "")]
public string Domain
{
get
{
return (string)base["domain"];
}
}
[ConfigurationProperty("reAuthenticate", DefaultValue = false)]
public bool ReAuthenticate
{
get
{
return (bool)base["reAuthenticate"];
}
}
[TypeConverter(typeof(TimeSpanMinutesConverter)), ConfigurationProperty("timeout", DefaultValue = "30"), PositiveTimeSpanValidator]
public TimeSpan Timeout
{
get
{
return (TimeSpan)base["timeout"];
}
}
}
public class BasicAuthenticationIdentity : IIdentity
{
public BasicAuthenticationIdentity(FormsAuthenticationTicket ticket)
{
if (ticket == null)
throw new ArgumentNullException("ticket");
Ticket = ticket;
}
public FormsAuthenticationTicket Ticket;
public string AuthenticationType
{
get
{
return BasicAuthenticationSection.SectionName;
}
}
public bool IsAuthenticated
{
get
{
return true;
}
}
public string Name
{
get
{
return Ticket.Name;
}
}
}
public class BasicAuthenticationEventArgs : CancelEventArgs
{
public BasicAuthenticationEventArgs(HttpContext context, string login, string password, string realm)
{
if (context == null)
throw new ArgumentNullException("context");
Context = context;
Login = login;
Password = password;
Realm = realm;
}
public HttpContext Context { get; private set; }
public string Realm { get; private set; }
public string Login { get; private set; }
public string Password { get; private set; }
public IPrincipal User { get; set; }
}
}
Once this is installed server side, you can configure the WebClient to use Basic auth:
WebClient client = new WebClient();
client.Credentials = new NetworkCredential("username", "password");
There are numerous ways to share the cookie with the console application. Take a look at some ideas here:
http://netpl.blogspot.com/2008/02/clickonce-webservice-and-shared-forms.html
Another simple option would be to expose a web method which doesn't require any authentication, gets the username and password and returns the cookie to the client.
No matter what approach you take, your goal is to somehow get the forms cookie at the console application side. From there you are easily done as all you do is you attach the cookie to your requests. The web api will accept the cookie happily.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With