{"id":1223,"date":"2010-07-19T13:03:00","date_gmt":"2010-07-19T13:03:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/odatateam\/2010\/07\/19\/odata-and-authentication-part-4-server-side-hooks\/"},"modified":"2024-02-16T15:03:00","modified_gmt":"2024-02-16T22:03:00","slug":"odata-and-authentication-part-4-server-side-hooks","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/odata-and-authentication-part-4-server-side-hooks\/","title":{"rendered":"OData and Authentication \u2013 Part 4 \u2013 Server Side Hooks"},"content":{"rendered":"<p>If you secure an OData Service using Windows authentication &ndash; see <a href=\"http:\/\/blogs.msdn.com\/b\/astoriateam\/archive\/2010\/05\/10\/odata-and-authentication-part-2-windows-authentication.aspx\">Part 2<\/a> to learn how &ndash; everything works as expected out of the box.<\/p>\n<p>What however if you need a different authentication scheme?<\/p>\n<p>Well the answer as always depends upon your scenario. <\/p>\n<p>Broadly speaking what you need to do depends upon how your Data Service is hosted. You have three options:<\/p>\n<ol>\n<li>Hosted by IIS <\/li>\n<li>Hosted by WCF <\/li>\n<li>Hosted in a custom host <\/li>\n<\/ol>\n<p>But by far the most common scenario is&hellip;<\/p>\n<h3>Hosted by IIS<\/h3>\n<p>This is what you get when you deploy your WebApplication project &ndash; containing a Data Service &ndash; to IIS. <\/p>\n<p>At this point you have two realistic options:<\/p>\n<ul>\n<li>Create a custom HttpModule. <\/li>\n<li>Hook up to the DataServices ProcessingPipeline. <\/li>\n<\/ul>\n<p>Which is best? <\/p>\n<p>Undoubtedly the ProcessingPipeline option is easier to understand and has less moving parts. Which makes it an ideal solution for simple scenarios. <\/p>\n<p>But the ProcessingPipeline is only an option if it makes sense to allow anonymous access to the rest of website. Which is pretty unlikely unless the web application only exists to host the Data Service.<\/p>\n<h4>Using ProcessingPipeline.ProcessingRequest<\/h4>\n<p>Nevertheless the ProcessingPipeline approach is informative, and most of the code involved can be reused if you ever need to upgrade to a fully fledged HttpModule.<\/p>\n<p>So how do you use the ProcessingPipeline?<\/p>\n<p>Well the first step is to enable anonymous access to your site in IIS:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2010\/07\/1106.IISAuth_thumb.png\"><img decoding=\"async\" height=\"89\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2010\/07\/1106.IISAuth_thumb.png\" alt=\"IISAuth\" border=\"0\" title=\"IISAuth\" style=\"border-right-width: 0px;border-top-width: 0px;border-bottom-width: 0px;border-left-width: 0px\" \/><\/a> <\/p>\n<p>Next you hookup to the ProcessingPipeline.ProcessingRequest event:<\/p>\n<p><span style=\"font-family: Courier New\">public class ProductService : DataService&lt;Context&gt; <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; public ProductService() <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.ProcessingPipeline.ProcessingRequest += new EventHandler&lt;DataServiceProcessingPipelineEventArgs&gt;(OnRequest); <br \/>&nbsp;&nbsp;&nbsp; }<\/span><\/p>\n<p>Then you need some code in the OnRequest event handler to do the authentication:<\/p>\n<p><span style=\"font-family: Courier New\">void OnRequest(object sender, <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataServiceProcessingPipelineEventArgs e) <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; if (!CustomAuthenticationProvider.Authenticate(HttpContext.Current)) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;401 Unauthorized&#8221;); <br \/>}<\/span><\/p>\n<p>In this code we call into a CustomAuthenticationProvider.Authenticate() method. <\/p>\n<p>If everything is okay &ndash; and what that means depends upon the authentication scheme &#8211; the request is allowed to continue. <\/p>\n<p>If not we throw a DataServiceException which ends up as a 401 Unauthorized response on the client.<\/p>\n<p>Because we are hosted in IIS our Authenticate() method has access to the current Request via the HttpContext.Current.Request.<\/p>\n<p>My pseudo-code, which assumes some sort of claims based security, looks like this:<\/p>\n<p><span style=\"font-family: Courier New\">public static bool Authenticate(HttpContext context) <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; if (!context.Request.Headers.AllKeys.Contains(&#8220;Authorization&#8221;)) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <\/p>\n<p>&nbsp;&nbsp;&nbsp; \/\/ Remember claims based security should be only be <br \/>&nbsp;&nbsp;&nbsp; \/\/ <\/span><span style=\"font-family: Courier New\">used over HTTPS&nbsp; <br \/>&nbsp;&nbsp;&nbsp; <\/span><span style=\"font-family: Courier New\">if (!context.Request.IsSecureConnection) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false;<\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; string authHeader = context.Request.Headers[&#8220;Authorization&#8221;]; <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; IPrincipal principal = null; <br \/>&nbsp;&nbsp;&nbsp; if (TryGetPrinciple(authHeader, out principal)) <br \/>&nbsp;&nbsp;&nbsp; {&nbsp; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context.User = principal; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true;<\/span><span style=\"font-family: Courier New\"> <br \/>&nbsp;&nbsp;&nbsp; }&nbsp; <br \/>&nbsp;&nbsp;&nbsp; return false;<\/span><span style=\"font-family: Courier New\">&nbsp; <br \/>}<\/span><\/p>\n<p>What happens in TryGetPrincipal() is completely dependent upon your auth scheme. <\/p>\n<p>Because this post is about server hooks, not concrete scenarios, our TryGetPrincipal implementation is clearly NOT meant for production (!):<\/p>\n<p><span style=\"font-family: Courier New\">private static bool TryGetPrincipal( <br \/>&nbsp;&nbsp; string authHeader, <br \/>&nbsp;&nbsp; out IPrincipal principal) <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; \/\/ <br \/>&nbsp;&nbsp;&nbsp; \/\/ WARNING: <br \/>&nbsp;&nbsp;&nbsp; \/\/ <\/span><span style=\"font-family: Courier New\">our naive &ndash; easily mislead authentication scheme <br \/>&nbsp;&nbsp;&nbsp; \/\/ blindly trusts the caller. <br \/>&nbsp;&nbsp;&nbsp; \/\/ a header that looks like this: <br \/>&nbsp;&nbsp;&nbsp; \/\/ ADMIN username <br \/>&nbsp;&nbsp;&nbsp; \/\/ will result in someone being authenticated as an <br \/>&nbsp;&nbsp;&nbsp; \/\/ administrator with<\/span><span style=\"font-family: Courier New\"> an identity of &lsquo;username&rsquo;<\/span><span style=\"font-family: Courier New\"> <br \/>&nbsp;&nbsp;&nbsp; \/\/ i.e. not exactly secure!!! <br \/>&nbsp;&nbsp;&nbsp; \/\/ <\/span><span style=\"font-family: Courier New\"><br \/>&nbsp;&nbsp;&nbsp; var protocolParts = authHeader.Split(&#8216; &#8216;); <br \/>&nbsp;&nbsp;&nbsp; if (protocolParts.Length != 2) <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; principal = null; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <br \/>&nbsp;&nbsp;&nbsp; } <br \/>&nbsp;&nbsp;&nbsp; else if (protocolParts[0] == &#8220;ADMIN&#8221;) <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; principal = new CustomPrincipal( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; protocolParts[1], <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;Administrator&#8221;, &#8220;User&#8221; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ); <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true; <br \/>&nbsp;&nbsp;&nbsp; } <br \/>&nbsp;&nbsp;&nbsp; else if (protocolParts[0] == &#8220;USER&#8221;) <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; principal = new CustomPrincipal( <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; protocolParts[1], <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;User&#8221; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ); <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return true; <br \/>&nbsp;&nbsp;&nbsp; } <br \/>&nbsp;&nbsp;&nbsp; else <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; principal = null; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return false; <br \/>&nbsp;&nbsp;&nbsp; } <br \/>}<\/span><\/p>\n<p>Don&rsquo;t worry though as this series progresses we will look at enabling real schemes like Custom Basic Auth, OAuthWrap, OAuth 2.0 and OpenId.<\/p>\n<h4>Creating a custom Principal and Identity<\/h4>\n<p>Strictly speaking you don&rsquo;t need to set the Current.User, you could just allow or reject the request. But we want to access the User and their roles (or claims) for authorization purposes, so our TryGetPrincipal code needs an implementation of IPrincipal and IIdentity:<\/p>\n<p><span style=\"font-family: Courier New\">public class CustomPrincipal: IPrincipal <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; string[] _roles; <br \/>&nbsp;&nbsp;&nbsp; IIdentity _identity; <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; public CustomPrincipal(string name, params string[] roles) <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this._roles = roles; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this._identity = new CustomIdentity(name); <br \/>&nbsp;&nbsp;&nbsp; } <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; public IIdentity Identity <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get { return _identity; } <br \/>&nbsp;&nbsp;&nbsp; } <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; public bool IsInRole(string role) <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return _roles.Contains(role); <br \/>&nbsp;&nbsp;&nbsp; } <br \/>} <br \/>public class CustomIdentity: IIdentity <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; string _name; <br \/><\/span><span style=\"font-family: Courier New\"><br \/>&nbsp;&nbsp;&nbsp; public CustomIdentity(string name) <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this._name = name; <br \/>&nbsp;&nbsp;&nbsp; } <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; string IIdentity.AuthenticationType <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get { return &#8220;Custom SCHEME&#8221;; } <br \/>&nbsp;&nbsp;&nbsp; } <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; bool IIdentity.IsAuthenticated <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get { return true; } <br \/>&nbsp;&nbsp;&nbsp; } <\/span><\/p>\n<p><span style=\"font-family: Courier New\">&nbsp;&nbsp;&nbsp; string IIdentity.Name <br \/>&nbsp;&nbsp;&nbsp; { <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get { return _name; } <br \/>&nbsp;&nbsp;&nbsp; } <br \/>} <br \/><\/span><\/p>\n<p>Now my authorization logic only has to worry about authenticated users, and can implement fine grained access control.<\/p>\n<p>For example if only Administrators can see products, we can enforce that in a QueryInterceptor like this:<\/p>\n<p><span style=\"font-family: Courier New\">[QueryInterceptor(&#8220;Products&#8221;)] <br \/>public Expression&lt;Func&lt;Product, bool&gt;&gt; OnQueryProducts() <br \/>{ <br \/>&nbsp;&nbsp;&nbsp; var user = HttpContext.Current.User; <br \/>&nbsp;&nbsp;&nbsp; if (user.IsInRole(&#8220;Administrator&#8221;)) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (Product p) =&gt; true; <br \/>&nbsp;&nbsp;&nbsp; else <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (Product p) =&gt; false; <br \/>}<\/span><\/p>\n<h3>Summary<\/h3>\n<p>In this post you saw how to add custom authentication logic *inside* the Data Service using the ProcessingPipeline.ProcessRequest event.<\/p>\n<p>Generally though when you want to integrate security across your website and your Data Service, you should put your authentication logic *under* the Data Service, in a HttpModule.<\/p>\n<p>More on that <a href=\"http:\/\/bit.ly\/aEhqp8\">next time&hellip;<\/a><\/p>\n<p><strong><a href=\"https:\/\/twitter.com\/adjames\">Alex James<\/a><\/strong><br \/>Program Manager<br \/>Microsoft<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you secure an OData Service using Windows authentication &ndash; see Part 2 to learn how &ndash; everything works as expected out of the box. What however if you need a different authentication scheme? Well the answer as always depends upon your scenario. Broadly speaking what you need to do depends upon how your Data [&hellip;]<\/p>\n","protected":false},"author":512,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[15,23,48],"class_list":["post-1223","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata","tag-authentication","tag-data-services","tag-odata"],"acf":[],"blog_post_summary":"<p>If you secure an OData Service using Windows authentication &ndash; see Part 2 to learn how &ndash; everything works as expected out of the box. What however if you need a different authentication scheme? Well the answer as always depends upon your scenario. Broadly speaking what you need to do depends upon how your Data [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/1223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/512"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=1223"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/1223\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3253"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=1223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=1223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=1223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}