This has worked well with OperationHandlers, but with ASP.NET Web API these are no longer an option.
So - my question is, what is the best way of acheiving the same without OperationHandlers, i.e. "Getting the details from the iPrincipal (domain, username, password) when using Basic Authentication"
One possible place for that is a message handler. In the handler, check whether the request contains the "Authorization: Basic XXXX" header, and if so, add a new custom principal to one of the request properties. That property can later be used
(i.e., in an authorization filter, or in the operation itself) to retrieve the principal associated with the request.
Also, if you set the principal in the request using the System.Web.Http.Hosting.HttpPropertyKeys.UserPrincipalKey name, the principal can be retrieved by the (extension) method HttpRequestMessage.GetUserPrincipal (defined in the namespace System.Web.Http).
I am trying to change things to work with this new ASP Web API style and I have the same question as larsm11. Also, I am using the self-hosted method and I don't know how to get the https schema working.
static void Main(string[] args)
{
var config = new HttpSelfHostConfiguration("https://localhost:443");
config.Routes.MapHttpRoute(
"API Default", "api/{controller}/{id}",
new { id = RouteParameter.Optional });
using (var server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
}
}
This code crashes on OpenAsync(). The uri schema is not valid. Any help would be appreciated.
EDIT: Fixed. With the old approach, I binded the certificate by console. Now, I have to load it from code:
public class BasicAuthMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Authorization == null || request.Headers.Authorization.Scheme != "Basic")
{
return
Task<HttpResponseMessage>.Factory.StartNew(
() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
var encoded = request.Headers.Authorization.Parameter;
var encoding = Encoding.GetEncoding("iso-8859-1");
var userPass = encoding.GetString(Convert.FromBase64String(encoded));
int sep = userPass.IndexOf(':');
var username = userPass.Substring(0, sep);
var identity = new GenericIdentity(username, "Basic");
request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[] { }));
return base.SendAsync(request, cancellationToken);
}
}
In program.cs
var config = new HttpSelfHostConfiguration("http://localhost:8080");
config.MessageHandlers.Add(new BasicAuthMessageHandler());
config.UserNamePasswordValidator = new AuthValidator();
Finally, you can get the Principal object from the ApiController with:
IPrincipal principal = ControllerContext.Request.GetUserPrincipal();
I'm not sure if I should return Unauthorized or Forbidden tough.
Hope this helps :)
EDIT: I have a question regarding this new approach. This code forces basic auth for all ApiControllers of this server, right? With the old approach you could disable the basic auth for each resource. How could we accomplish this now?
Thanks a lot. I ended up with very much the same implementstion also. But, and I'm also stuck with the left over question on how to avoid running all handlers on all controllers. I'd rather use configuration on routes than IF and CASE i each Handler. Thoughts
anyone.
Following inmykingdom recommendation I have moved the code to an AuthorizationFilterAttribute so I can control authentication per action. But I am having a weird problem. Basically, my code is this:
public class RequireBasicAuth : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null ||
actionContext.Request.Headers.Authorization.Scheme != "Basic")
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
return;
}
.... more code there
}
}
So... If Basic auth is not present, I return a response with 401 status code. But, to follow the standard handshake, I should add the wwwauthenticate header. Something like: "WWW-Authenticate: Basic realm="Action secure"". The code above works perfectly but if I add the www-authenticate header, I get this in fiddler "[Fiddler] ReadResponse() failed: The server did not return a response for this request.". The code I am using to add this header is:
larsm11
Member
12 Points
8 Posts
HTTPS and Basic Authentication without Operationshandler
Feb 20, 2012 05:28 PM|LINK
I've been building on this article http://pfelix.wordpress.com/2011/04/21/wcf-web-api-self-hosting-https-and-http-basic-authentication/#comment-232 in order to integrate WCF Web API with HTTPS and Basic Authentication.
This has worked well with OperationHandlers, but with ASP.NET Web API these are no longer an option.
So - my question is, what is the best way of acheiving the same without OperationHandlers, i.e. "Getting the details from the iPrincipal (domain, username, password) when using Basic Authentication"
/LM
CarlosFiguei...
Member
99 Points
19 Posts
Microsoft
Re: HTTPS and Basic Authentication without Operationshandler
Feb 20, 2012 06:10 PM|LINK
One possible place for that is a message handler. In the handler, check whether the request contains the "Authorization: Basic XXXX" header, and if so, add a new custom principal to one of the request properties. That property can later be used (i.e., in an authorization filter, or in the operation itself) to retrieve the principal associated with the request.
CarlosFiguei...
Member
99 Points
19 Posts
Microsoft
Re: HTTPS and Basic Authentication without Operationshandler
Feb 20, 2012 06:39 PM|LINK
Also, if you set the principal in the request using the System.Web.Http.Hosting.HttpPropertyKeys.UserPrincipalKey name, the principal can be retrieved by the (extension) method HttpRequestMessage.GetUserPrincipal (defined in the namespace System.Web.Http).
larsm11
Member
12 Points
8 Posts
Re: HTTPS and Basic Authentication without Operationshandler
Feb 21, 2012 09:51 AM|LINK
Thanks Carlos.
Any chance your could provide some pseudo code for this pipeline/approach.
/LarsM
hilbertZg
Member
28 Points
14 Posts
Re: HTTPS and Basic Authentication without Operationshandler
Feb 21, 2012 10:00 AM|LINK
I am trying to change things to work with this new ASP Web API style and I have the same question as larsm11. Also, I am using the self-hosted method and I don't know how to get the https schema working.
static void Main(string[] args) { var config = new HttpSelfHostConfiguration("https://localhost:443"); config.Routes.MapHttpRoute( "API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); using (var server = new HttpSelfHostServer(config)) { server.OpenAsync().Wait(); Console.WriteLine("Press Enter to quit."); Console.ReadLine(); } }EDIT: Fixed. With the old approach, I binded the certificate by console. Now, I have to load it from code:
config.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "be0032743105b59b0bf4f1a076c0d9d7ed98d566");hilbertZg
Member
28 Points
14 Posts
Re: HTTPS and Basic Authentication without Operationshandler
Feb 21, 2012 12:01 PM|LINK
@LarsM, this is my code for the basic auth.
public class BasicAuthMessageHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Headers.Authorization == null || request.Headers.Authorization.Scheme != "Basic") { return Task<HttpResponseMessage>.Factory.StartNew( () => new HttpResponseMessage(HttpStatusCode.Unauthorized)); } var encoded = request.Headers.Authorization.Parameter; var encoding = Encoding.GetEncoding("iso-8859-1"); var userPass = encoding.GetString(Convert.FromBase64String(encoded)); int sep = userPass.IndexOf(':'); var username = userPass.Substring(0, sep); var identity = new GenericIdentity(username, "Basic"); request.Properties.Add(HttpPropertyKeys.UserPrincipalKey, new GenericPrincipal(identity, new string[] { })); return base.SendAsync(request, cancellationToken); } }In program.cs
var config = new HttpSelfHostConfiguration("http://localhost:8080"); config.MessageHandlers.Add(new BasicAuthMessageHandler()); config.UserNamePasswordValidator = new AuthValidator();Finally, you can get the Principal object from the ApiController with:
I'm not sure if I should return Unauthorized or Forbidden tough.
Hope this helps :)
EDIT: I have a question regarding this new approach. This code forces basic auth for all ApiControllers of this server, right? With the old approach you could disable the basic auth for each resource. How could we accomplish this now?
Troy Dai
Member
8 Points
4 Posts
Microsoft
Re: HTTPS and Basic Authentication without Operationshandler
Feb 21, 2012 11:48 PM|LINK
Hi LM,
There is another option: you can implement custom http model to do basic authentication.
For example, here's an implementation of an IHttpModule.
public class CustomBasicAuthenticationModule : IHttpModule { private const string HTTP_HEADER_AUTHORIZATION = "Authorization"; private readonly CustomUsernamePasswordValidator _usernamePasswordValidator; public CustomBasicAuthenticationModule() { _usernamePasswordValidator = new CustomUsernamePasswordValidator(); } public void Dispose() { // no op } public void Init(HttpApplication context) { context.AuthenticateRequest += (s, args) => { var currentContext = HttpContext.Current; if (!currentContext.Request.IsSecureConnection) { throw new HttpException(403, "SSL required for Basic Authentication"); } var authHeader = context.Request.Headers[HTTP_HEADER_AUTHORIZATION]; if (String.IsNullOrEmpty(authHeader)) { currentContext.Response.StatusCode = 401; currentContext.Response.End(); return; } Tuple<string, string> credentials = GetCredentials(authHeader); IPrincipal principal; if (!_usernamePasswordValidator.Validate(credentials.Item1, credentials.Item2, out principal)) { // we failed to validate the username/password currentContext.Response.StatusCode = 401; currentContext.Response.End(); return; } // otherwise, we are authenticated, set the current context currentContext.User = principal; }; context.EndRequest += (s, args) => { var currentContext = HttpContext.Current; if (currentContext.Response.StatusCode == 401) { currentContext.Response.StatusCode = 401; currentContext.Response.AddHeader( "WWW-Authenticate", String.Format("Basic realm=\"{0}\"", "Home")); } }; } // parse the authHeader into username and password private static Tuple<string, string> GetCredentials(string authHeader) { // strip out "basic" var encodedUsernameAndPwd = authHeader.Substring(6).Trim(); var encoding = Encoding.GetEncoding("iso-8859-1"); var usernameAndPwd = encoding.GetString(Convert.FromBase64String(encodedUsernameAndPwd)); var usernameAndPwdElements = usernameAndPwd.Split(new[] { ':' }); return new Tuple<string, string>(usernameAndPwdElements[0], usernameAndPwdElements[1]); } }Then in web.config, adding following setting under <configuration>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true">
<!-- use the custom username password provider to validate the incoming request -->
<add name="CustomBasicAuthModule" type="WebHostSecurity.Sample.CustomBasicAuthenticationModule"/>
</modules>
</system.webServer>
You may also utilize the authorization attribute to enforce authorization on action
It's not a full-blown case, but hopefully i helps.
larsm11
Member
12 Points
8 Posts
Re: HTTPS and Basic Authentication without Operationshandler
Feb 22, 2012 07:14 AM|LINK
inmykingdom
Member
105 Points
34 Posts
Re: HTTPS and Basic Authentication without Operationshandler
Feb 22, 2012 10:10 AM|LINK
You could write a custom AuthorizeAttribute and add that to the controllers that need it. ActionFilters is an alternative to the MessageHandlers.
I wrote this down "HttpOperationHandler replaced by MVC ActionFilters." after watching http://www.viddler.com/v/58195ad7
hilbertZg
Member
28 Points
14 Posts
Re: HTTPS and Basic Authentication without Operationshandler
Feb 22, 2012 01:42 PM|LINK
Following inmykingdom recommendation I have moved the code to an AuthorizationFilterAttribute so I can control authentication per action. But I am having a weird problem. Basically, my code is this:
public class RequireBasicAuth : AuthorizationFilterAttribute { public override void OnAuthorization(HttpActionContext actionContext) { if (actionContext.Request.Headers.Authorization == null || actionContext.Request.Headers.Authorization.Scheme != "Basic") { actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized); return; } .... more code there } }So... If Basic auth is not present, I return a response with 401 status code. But, to follow the standard handshake, I should add the wwwauthenticate header. Something like: "WWW-Authenticate: Basic realm="Action secure"". The code above works perfectly but if I add the www-authenticate header, I get this in fiddler "[Fiddler] ReadResponse() failed: The server did not return a response for this request.". The code I am using to add this header is:
actionContext.Response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic"));I have tried adding the realm, thowing an HttpResponseException... nothing works. If I add the www-Authenticate header, the client gets nothing.
Any ideas?