I have a WCF HTTP REST Service and I tie into it with an HTTP client in a different programming language who writes its own custom HTTP. I would like to add WWW-Authenticate basic authentication support to my WCF service.
My methods look like this:
[WebInvoke(UriTemplate = "widgets", Method = "POST")]
public XElement CreateWidget(XElement e)
{
...
}
Is it possible for me to somehow filter incoming HTTP requests so I can check for a valid Basic auth string before it hits each of the REST methods like CreateWidget
above?
Note: My auth info is stord in my database.
Basically I want to check for this in the request headers:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
and then I can myself parse that string and validate the u/p in the database.
The web.config file is as follows:
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add name="DatabaseConnectionString" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Database;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="10485760" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" maxReceivedMessageSize="1048576" maxBufferSize="1048576" />
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
Here is what you need to do to secure it: Add a new WebHttpBinding configuration that has security mode set to Transport . Assign that new WebHttpBinding configuration to the your Service Endpoint binding. Make sure that your RESTful service can only be accessed via HTTPS by setting httpGetEnabled="false" .
basic("your username", "your password"). get("your end point URL"); In the given method you need to append the method of authentication specification followed by the basic HTTP auth where you will pass the credentials as the parameters. Another type of basic authentication is preemptive which we will discuss next.
I was also interested in custom authentication in a REST HTTP WCF service and finally got it to work.
That being said my code will give you a way to get it working, but I recommend reading this guide which explains everything in more depth: http://wcfsecurityguide.codeplex.com/
First, change the system.web
portion of your Web.Config to look like this:
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="10485760" />
<authentication mode="None"></authentication>
<httpModules>
<add name="BasicAuthenticationModule" type="YourNamespace.UserNameAuthenticator" />
</httpModules>
</system.web>
Then add another file to your project: UserNameAuthenticator.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Security.Principal;
using System.ServiceModel.Activation;
namespace YourNamespace
{
public class UserNameAuthenticator : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
application.AuthorizeRequest += new EventHandler(this.OnAuthorizationRequest);
application.EndRequest += new EventHandler(this.OnEndRequest);
}
public bool CustomAuth(string username, string password)
{
//TODO: Implement your custom auth logic here
return true;
}
public string[] GetCustomRoles(string username)
{
return new string[] { "read", "write" };
}
public void OnAuthorizationRequest(object source, EventArgs eventArgs)
{
HttpApplication app = (HttpApplication)source;
//If you want to handle authorization differently from authentication
}
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
{
HttpApplication app = (HttpApplication)source;
//the Authorization header is checked if present
string authHeader = app.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader))
{
string authStr = app.Request.Headers["Authorization"];
if (authStr == null || authStr.Length == 0)
{
// No credentials; anonymous request
return;
}
authStr = authStr.Trim();
if (authStr.IndexOf("Basic", 0) != 0)
{
//header not correct we do not authenticate
return;
}
authStr = authStr.Trim();
string encodedCredentials = authStr.Substring(6);
byte[] decodedBytes = Convert.FromBase64String(encodedCredentials);
string s = new ASCIIEncoding().GetString(decodedBytes);
string[] userPass = s.Split(new char[] { ':' });
string username = userPass[0];
string password = userPass[1];
//the user is validated against the SqlMemberShipProvider
//If it is validated then the roles are retrieved from the
//role provider and a generic principal is created
//the generic principal is assigned to the user context
// of the application
if (CustomAuth(username, password))
{
string[] roles = GetCustomRoles(username);
app.Context.User = new GenericPrincipal(new
GenericIdentity(username, "Membership Provider"), roles);
}
else
{
DenyAccess(app);
return;
}
}
else
{
//the authorization header is not present
//the status of response is set to 401 and it ended
//the end request will check if it is 401 and add
//the authentication header so the client knows
//it needs to send credentials to authenticate
app.Response.StatusCode = 401;
app.Response.End();
}
}
public void OnEndRequest(object source, EventArgs eventArgs)
{
if (HttpContext.Current.Response.StatusCode == 401)
{
//if the status is 401 the WWW-Authenticated is added to
//the response so client knows it needs to send credentials
HttpContext context = HttpContext.Current;
context.Response.StatusCode = 401;
context.Response.AddHeader("WWW-Authenticate", "Basic Realm");
}
}
private void DenyAccess(HttpApplication app)
{
app.Response.StatusCode = 401;
app.Response.StatusDescription = "Access Denied";
// error not authenticated
app.Response.Write("401 Access Denied");
app.CompleteRequest();
}
} // End Class
} //End Namespace
I've had similar problems and found a lot of different approaches, especially the cross domain calls, together with basic authentication seems to be a bit of a challenge. Jquery for example first issues an OPTIONS call to verify it the POST is allowed. Wcf normally declines this request and you get a strange error.
I've finally got it working and you can download sample code from my blog: http://sameproblemmorecode.blogspot.com/2011/10/creating-secure-restfull-wcf-service.html
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