I have some core ASP code that I want to expose both by secure web pages (using Forms Authentication) and via web services (using Basic Authentication).
The solution that I've come up with seems to work, but am I missing anything here?
First, the whole site runs under HTTPS.
Site is set to use Forms authentication in web.config
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" timeout="2880"/>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
Then I override the AuthenticateRequest in Global.asax, to trigger Basic Authentication on the web service pages:
void Application_AuthenticateRequest(object sender, EventArgs e)
{
//check if requesting the web service - this is the only page
//that should accept Basic Authentication
HttpApplication app = (HttpApplication)sender;
if (app.Context.Request.Path.StartsWith("/Service/MyService.asmx"))
{
if (HttpContext.Current.User != null)
{
Logger.Debug("Web service requested by user " + HttpContext.Current.User.Identity.Name);
}
else
{
Logger.Debug("Null user - use basic auth");
HttpContext ctx = HttpContext.Current;
bool authenticated = false;
// look for authorization header
string authHeader = ctx.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
// extract credentials from header
string[] credentials = extractCredentials(authHeader);
// because i'm still using the Forms provider, this should
// validate in the same way as a forms login
if (Membership.ValidateUser(credentials[0], credentials[1]))
{
// create principal - could also get roles for user
GenericIdentity id = new GenericIdentity(credentials[0], "CustomBasic");
GenericPrincipal p = new GenericPrincipal(id, null);
ctx.User = p;
authenticated = true;
}
}
// emit the authenticate header to trigger client authentication
if (authenticated == false)
{
ctx.Response.StatusCode = 401;
ctx.Response.AddHeader(
"WWW-Authenticate",
"Basic realm=\"localhost\"");
ctx.Response.Flush();
ctx.Response.Close();
return;
}
}
}
}
private string[] extractCredentials(string authHeader)
{
// strip out the "basic"
string encodedUserPass = authHeader.Substring(6).Trim();
// that's the right encoding
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string userPass = encoding.GetString(Convert.FromBase64String(encodedUserPass));
int separator = userPass.IndexOf(':');
string[] credentials = new string[2];
credentials[0] = userPass.Substring(0, separator);
credentials[1] = userPass.Substring(separator + 1);
return credentials;
}
Unlike Form-Based Authentication, Basic Authentication DO NOT use cookies, hence there is no concept of a session or logging out a user, which means each request has to carry that header in order to be authenticated. Form-Based Authentication in the other hand is not formalized by any RFC.
Difference between Basic Authentication and Windows authentication. Windows authentication authenticates the user by validating the credentials against the user account in a Windows domain. Basic authentication verifies the credentials that are provided in a form against the user account that is stored in a database.
Passport authentication relies on a centralized service provided by Microsoft. Passport authentication identifies a user with using his or her e-mail address and a password and a single Passport account can be used with many different Web sites.
Basic Authentication is a method for an HTTP user agent (e.g., a web browser) to provide a username and password when making a request. When employing Basic Authentication, users include an encoded string in the Authorization header of each request they make.
.Net 4.5 has a new Response property: SuppressFormsAuthenticationRedirect. When set to true it prevents redirecting a 401 response to the login page of the website. You can use the following code snippet in your global.asax.cs to enable Basic Authentication for e.g. the /HealthCheck folder.
/// <summary>
/// Authenticates the application request.
/// Basic authentication is used for requests that start with "/HealthCheck".
/// IIS Authentication settings for the HealthCheck folder:
/// - Windows Authentication: disabled.
/// - Basic Authentication: enabled.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="System.EventArgs"/> that contains the event data.</param>
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
var application = (HttpApplication)sender;
if (application.Context.Request.Path.StartsWith("/HealthCheck", StringComparison.OrdinalIgnoreCase))
{
if (HttpContext.Current.User == null)
{
var context = HttpContext.Current;
context.Response.SuppressFormsAuthenticationRedirect = true;
}
}
}
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