How to access ClientCertificate in a host agnostic manner
When you choose to use Client Certificate as your client credential over SSL, you want to retrieve the x509 Certificate on the server side to do authorization. It is very simple with the latest Web API bits. If you have access to the request message anywhere in the pipeline, such as message handler, action filter or action. you can simply call the extension method shown below. Please be careful that the GetClientCertificate method might return null if the client does not send an valid certificate.
Now getting the client certificate is easy, but how to set it up correctly on self host and web host is not that easy. The following instructions walk you through the process step by step.
Web Host
Step 1: go to the IIS manager, SSL setting, check require client certificate
Step 2: register a custom message handler and verify that the client certificate is something you expect.
Code Snippet
- public class CustomCertificateMessageHandler : DelegatingHandler
- {
- protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- X509Certificate cert = request.GetClientCertificate();
- if (cert != null)
- {
- if (cert.Subject.Contains("Some Name you are expecting"))
- {
- Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(cert.Subject), new[] { "Administrators" });
- }
- }
- return base.SendAsync(request, cancellationToken);
- }
- }
Step 3: register the custom message handler.
Code Snippet
- // Register a message to turn the client cert to a admin role
- GlobalConfiguration.Configuration.MessageHandlers.Add(new CustomCertificateMessageHandler());
Step 4: Write a RequireAdminAttribute
Code Snippet
- public class RequireAdminAttribute : AuthorizationFilterAttribute
- {
- public override void OnAuthorization(HttpActionContext context)
- {
- // do authorization based on the principle.
- IPrincipal principal = Thread.CurrentPrincipal;
- if (principal == null || !principal.IsInRole("Administrators"))
- {
- context.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
- }
- }
- }
Step 5: Add the RequireAdminAttribute to the action.
Code Snippet
- public class HomeController : ApiController
- {
- [RequireAdmin]
- public HttpResponseMessage Get()
- {
- return new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent("Default User")
- };
- }
- public HttpResponseMessage Post()
- {
- return new HttpResponseMessage(HttpStatusCode.OK)
- {
- Content = new StringContent("User Posted")
- };
- }
- }
Now your GET action can only be accessed with the administrators.
Self Host
Step 1: Tell the WCF transport, I want to client to send certificate over SSL.
Code Snippet
- // client cert
- config.ClientCredentialType = HttpClientCredentialType.Certificate;
Step 2: Write some client code to pick a certificate to send
Code Snippet
- WebRequestHandler handler = new WebRequestHandler();
- handler.ClientCertificateOptions = ClientCertificateOption.Manual; // this would pick from the Current user store
- handler.ClientCertificates.Add(GetClientCertificate());
- handler.UseDefaultCredentials = true;
- HttpClient client = new HttpClient(handler);
Implement the GetClientCertificate to retrieve a certificate programmatically.
Then repeat step 2-5 from the web host section to complete the experience.
Hope this helps.
Comments
Anonymous
December 18, 2012
Does the certificate have to be a valid certificate?Anonymous
January 31, 2014
If you require SSL and a client cert why would GetClientCertificate() result in null? IIS would not allow the request to continue.Anonymous
May 27, 2014
Is there any sample code that shows how a javascript or .NET client can send a certificate as part of an http request.