{"id":1033,"date":"2011-01-20T16:38:00","date_gmt":"2011-01-20T16:38:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/odatateam\/2011\/01\/20\/odata-and-oauth-protecting-an-odata-service-using-oauth-2-0\/"},"modified":"2024-02-16T13:23:09","modified_gmt":"2024-02-16T20:23:09","slug":"odata-and-oauth-protecting-an-odata-service-using-oauth-2-0","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/odata-and-oauth-protecting-an-odata-service-using-oauth-2-0\/","title":{"rendered":"OData and OAuth \u2013 protecting an OData Service using OAuth 2.0"},"content":{"rendered":"<p>In this post you will learn how to create an OData service that is protected using OAuth 2.0, which is the OData team&rsquo;s official recommendation in these scenarios:<\/p>\n<ul>\n<li><b>Delegation:<\/b> In a delegation scenario a third party (generally an application) is granted access to a user&rsquo;s resources without the user disclosing their credentials (username and password) to the third party. <\/li>\n<li><b>Federation:<\/b> In a federation scenario a user&rsquo;s credentials on one domain (perhaps their corporate network) implies access to resources on a resource domain (say a data provider). They key though is that the credentials used (if any) on the resource domain are not disclosed to the end users and the user never discloses their credentials to the resource domain either.<\/li>\n<\/ul>\n<p>&nbsp;So if your scenarios is one of the above or some slight variation we recommend that you use OAuth 2.0 to protect your service, it provides the utmost flexibility and power.<\/p>\n<p>To explore this scenario we are going to walkthrough a real-world scenario, from end to end.<b><\/b><\/p>\n<p><b><\/b><\/p>\n<h2><b>The Scenario <\/b><\/h2>\n<p>We&rsquo;re going to create an OData service based on this Entity Framework model for managing a user&rsquo;s Favorite Uris:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/5518.image_thumb_44AE2E89.png\"><img decoding=\"async\" height=\"313\" width=\"454\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/5518.image_thumb_44AE2E89.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a><\/p>\n<p>As you can see this is a pretty simple model with just Users and Favorites. <\/p>\n<p>Our service should not require its own username and password, which is a sure way to annoy users today. Instead it will rely on well-known third parties like Google and Yahoo, to provide the users identity. We&rsquo;ll use AppFabric Access Control Services (aka ACS) because it provides an easy way to bridge these third parties claims and rewrite them as a signed OAuth 2.0 Simple Web Token or SWT. <\/p>\n<p>The idea is that we will trust email-address claims issued by our ACS service via a SWT in the Authorization header of the request. We&rsquo;ll then use a HttpModule to convert that SWT into a WIF ClaimsPrincipal.<\/p>\n<p>Then our service&rsquo;s job will be to map the EmailAddress in the incoming claim to a User entity in the database via the User&rsquo;s EmailAddress property, and use that to enforce Business Rules.<\/p>\n<p><b><\/b><\/p>\n<h2><b>Business Rules<\/b><\/h2>\n<p>We need our Data Service to:<\/p>\n<ul>\n<li>Automatically create a new user whenever someone with an unknown email-address hits the system. <\/li>\n<li>Allow only administrators to query, create, update or delete users.<\/li>\n<li>Allow Administrators to see all favorites.<\/li>\n<li>Allow Administrators to update and delete all favorites.<\/li>\n<li>Allow Users to see public favorites and their private favorites.<\/li>\n<li>Allow Users to create new favorites. But the OwnerId, CreateDate and &lsquo;Public&rsquo; values should be set for them, i.e. what they user sends on the wire will be ignored.<\/li>\n<li>Allow Users to edit and delete only their favorites.<\/li>\n<li>Allow un-authenticated requests to query only public favorites.<\/li>\n<\/ul>\n<p><b><\/b><\/p>\n<h2><b>Implementation<\/b><\/h2>\n<h2><b>Prerequisites<\/b><\/h2>\n<ul>\n<li>Windows Server 2008 R2 or Windows 7 <\/li>\n<li>Visual Studio 2010 <\/li>\n<li>Internet Information Services (IIS) enabled with IIS Metabase and IIS6 Configuration Compatibility <\/li>\n<li>Windows Identity Foundation (WIF) (<a href=\"https:\/\/go.microsoft.com\/fwlink\/?LinkId=204657\">https:\/\/go.microsoft.com\/fwlink\/?LinkId=204657<\/a>) <\/li>\n<li>An existing Data Service Project that you want to protect. <\/li>\n<\/ul>\n<h2><b>Creating our Data Service<\/b><\/h2>\n<p>First we add a DataService that exposes our Entity Framework model like this: <\/p>\n<p><span style=\"font-family: Consolas\">public class Favorites : DataService&lt;FavoritesModelContainer&gt;<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ This method is called only once to initialize service-wide policies.<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; public static void InitializeService(DataServiceConfiguration config)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config.SetEntitySetAccessRule(&#8220;*&#8221;, EntitySetRights.All);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config.SetEntitySetPageSize(&#8220;*&#8221;, 100);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; } <br \/>}<\/span>&nbsp;<\/p>\n<h2><b>Authentication<\/b><\/h2>\n<h3><b>Configuring ACS<\/b><\/h3>\n<p>You can use <a href=\"https:\/\/portal.appfabriclabs.com\/\">https:\/\/portal.appfabriclabs.com\/<\/a> to create an AppFabric project, which will allow you to trial Access Control Services (or ACS) for free. The steps involved are:&nbsp;<\/p>\n<ol>\n<li>Sign in with your LiveId (or create a new one). Once you&rsquo;ve logged on you&rsquo;ll see something like this: \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/4520.image_thumb_436995AA.png\"><img decoding=\"async\" height=\"94\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/4520.image_thumb_436995AA.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Click the &lsquo;create a project&rsquo; link and choose a name for your project. \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/5287.image_thumb_4707B087.png\"><img decoding=\"async\" height=\"89\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/5287.image_thumb_4707B087.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Click on your new project: \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/0804.image_thumb_3B71F348.png\"><img decoding=\"async\" height=\"108\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/0804.image_thumb_3B71F348.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Click &lsquo;Add Service Namespace&rsquo; and choose a service namespace that is available: \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/7356.image_thumb_6CFD60DD.png\"><img decoding=\"async\" height=\"134\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/7356.image_thumb_6CFD60DD.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/p>\n<p>Then you will see this: <\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/7824.image_thumb_337A50E6.png\"><img decoding=\"async\" height=\"134\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/7824.image_thumb_337A50E6.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>You&rsquo;ll have to wait about 20 seconds for Azure to provision your namespace. Once it is active click on the &lsquo;Access Control&rsquo; the link: \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/4520.image_thumb_0BD3D4BC.png\"><img decoding=\"async\" height=\"207\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/4520.image_thumb_0BD3D4BC.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Click on &lsquo;Identity Providers&rsquo; which will allow you to configure ACS to accept identities from Google, Yahoo and Live, by clicking &lsquo;Add Identity Provider&rsquo; on the screen below: \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/3630.image_thumb_1939E7C2.png\"><img decoding=\"async\" height=\"122\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/3630.image_thumb_1939E7C2.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Once you&rsquo;ve added Google and Yahoo click on &lsquo;Return to Access Control Service&rsquo;<\/li>\n<li>Click on &lsquo;Relying Party Applications&rsquo;: \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/3036.image_thumb_4AC55557.png\"><img decoding=\"async\" height=\"123\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/3036.image_thumb_4AC55557.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/p>\n<p>NOTE: As you can see there is already a &lsquo;Relying Party Application&rsquo; called AccessControlManagement. That is the application we are currently using that manages our ACS instance. It trusts our ACS to make claims about the current user&rsquo;s identity. <\/p>\n<p>As you can see this management application thinks I am an administrator (top right corner). This is because I logged on to AppFabric using LiveId as <a href=\"mailto:odatademo@hotmail.com\">odatademo@hotmail.com<\/a> who is the owner of this Service Namespace. <\/p>\n<p>Now we can create a relying party &ndash; i.e. something to represent our OData favorites service &ndash; which will &lsquo;rely&rsquo; on ACS to make claims about who is making the request, to do this: <\/li>\n<li>Click on &lsquo;Add Relying Party Application&rsquo;. \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/1563.image_thumb_6A07FC2A.png\"><img decoding=\"async\" height=\"244\" width=\"241\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/1563.image_thumb_6A07FC2A.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <br \/><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/2465.image_thumb_776E0F30.png\"><img decoding=\"async\" height=\"88\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/2465.image_thumb_776E0F30.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Fill in the form like this and then click &lsquo;Save&rsquo; \n<p><b>Name: <\/b>Choose a name that represents your Application <br \/><b>Realm:<\/b> Choose the &lsquo;domain&rsquo; that you intend to host your application at. This will work even if you are testing on localhost first so long as web.config settings that control your OAuth security module match. <br \/><b>Return URL:<\/b> Choose some url relative to your domain, like in the above sample. Note this is not need by the server, this is only needed when we write a client &ndash; which we will do in the next blog post. You *will* need to change this value as you move from testing to live deployment, because your clients will actually follow this link. <br \/><b>Error URL: <\/b>Leave this blank<b> <br \/>Token format:<\/b> Choose SWT (i.e. a Simple Web Token which can be embedded in request headers). <br \/><b>Token lifetime (secs):<\/b> Leave at the default. <br \/><b>Identity providers:<\/b> Leave the default. <br \/><b>Rule groups:<\/b> Leave the default. <br \/><b>Token signing key:<\/b> Click &lsquo;Generate&rsquo; to produce a key or paste an existing key in. <br \/><b>Effective date:<\/b> Leave the default. <br \/><b>Expiration date:<\/b> Leave the default. <br \/><b><\/b><\/li>\n<li>Click on &lsquo;Return to Access Control Service&rsquo;.<\/li>\n<li>Click on &lsquo;Rule Groups&rsquo;<\/li>\n<li>Click on &lsquo;Default Rule Group for [your relying party]&rsquo; \n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/2475.image_thumb_3DEAFF39.png\"><img decoding=\"async\" height=\"135\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/2475.image_thumb_3DEAFF39.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<li>Click on &lsquo;Generate Rules&rsquo;<\/li>\n<li>Leave all Identity Providers checked and click the &lsquo;Generate&rsquo; button. \n<p>You should see these rules get generated automatically: <\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/7824.image_thumb_6F766CCE.png\"><img decoding=\"async\" height=\"92\" width=\"244\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/7824.image_thumb_6F766CCE.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;margin: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a> <\/li>\n<\/ol>\n<p>This set of rules will take claims from Google, Yahoo and Windows Live Id, and pass them through untouched by sign then with the Token Signing Key we generated earlier. <\/p>\n<p>Notice that LiveId claims don&rsquo;t include an &lsquo;emailaddress&rsquo; or &lsquo;name&rsquo;, so if we want to support LiveId our OAuth module on the server will need to figure out a way to convert a &lsquo;nameidentifier&rsquo; claim into a &lsquo;name&rsquo; and &lsquo;emailaddress&rsquo; which is beyond the scope of this blog post.<\/p>\n<p>At this point we&rsquo;ve finished configuring ACS, and we can configure our OData Service to trust it.<\/p>\n<h3><b>Server Building Blocks<\/b><\/h3>\n<p>We will rely on <a href=\"https:\/\/connect.microsoft.com\/site1168\/Downloads\">a sample<\/a> the WIF team recently released that includes a lot of useful OAuth 2.0 helper code. This code builds on WIF adding some very useful extensions.<\/p>\n<p>The most useful code for our purposes is a class called OAuthProtectionModule. This is a HttpModule that converts claims made via a Simple Web Token (SWT) in the incoming request&rsquo;s Authorization header into a ClaimsPrincipal which it then assigns to HttpContext.Current.User.<\/p>\n<p>If you&rsquo;ve been following the <a href=\"http:\/\/blogs.msdn.com\/b\/astoriateam\/archive\/tags\/authentication\/\">OData and Authentication series<\/a>, this general approach will be familiar to you. It means that by the time calls get to your OData service the HttpContext.Current.User has the current user (if any) and can be used to make decisions about whether to authorize the request.<\/p>\n<h3><b>Configuration<\/b><\/h3>\n<p>There is a lot of code in the WIF sample that we don&rsquo;t need. All you really need is the OAuthProtectionModule, so my suggestion is you pull that out into a separate project and grab classes from the sample as required. When I did that I moved things around a little and ended up with something that looked like this: <\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/1882.image_thumb_08060A1F.png\"><img decoding=\"async\" height=\"417\" width=\"285\" src=\"https:\/\/devblogs.microsoft.com\/odatateam\/wp-content\/uploads\/sites\/23\/2011\/01\/1882.image_thumb_08060A1F.png\" alt=\"image\" border=\"0\" title=\"image\" style=\"border-bottom: 0px;border-left: 0px;padding-left: 0px;padding-right: 0px;border-top: 0px;border-right: 0px;padding-top: 0px\" \/><\/a><\/p>\n<p>You might want to simplify the SamplesConfiguration class too, to remove unnecessary configuration information. I also decided to move the actual configuration into the web.config. When you make those changes you should end up with something like this:<\/p>\n<p><span style=\"font-family: Consolas\">public static class SamplesConfiguration<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; public static string ServiceNamespace<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ConfigurationManager.AppSettings[&#8220;ServiceNamespace&#8221;];<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; }<\/span><\/p>\n<p><span style=\"font-family: Consolas\">&nbsp;&nbsp; public static string RelyingPartyRealm<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ConfigurationManager.AppSettings[&#8220;RelyingPartyRealm&#8221;];<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; }<\/p>\n<p><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; public static string RelyingPartySigningKey<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ConfigurationManager.AppSettings[&#8220;RelyingPartySigningKey&#8221;];<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; }<\/span><\/p>\n<p><span style=\"font-family: Consolas\">&nbsp;&nbsp; public static string AcsHostUrl<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ConfigurationManager.AppSettings[&#8220;AcsHostUrl&#8221;];<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; } <br \/>}<\/span><\/p>\n<p>Then you need to add your configuration information to your <b>web.config<\/b>:<\/p>\n<p><span style=\"font-family: Consolas\">&lt;!&#8211; this is the Relying Party signing key we generated earlier, i.e. the key ACS will use to sign the SWT &ndash;&nbsp; <br \/>&nbsp;&nbsp;&nbsp;&nbsp; that our module can verify by signing and compariing &#8211;&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;add key=&#8221;RelyingPartySigningKey&#8221; value=&#8221;cx3SesVUdDE0yGYD+86BLzyffu0xPBRGUYR4wKPpklc=&#8221;\/&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;!&#8211; the dns name of the SWT issuer &#8211;&gt; <br \/>&lt;add key=&#8221;AcsHostUrl&#8221; value=&#8221;accesscontrol.appfabriclabs.com&#8221;\/&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;!&#8211; this is the your ACS ServiceNamespace of your OData service &#8211;&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;add key=&#8221;ServiceNamespace&#8221; value=&#8221;odatafavorites&#8221;\/&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;!&#8211; this is the intented url of your service (you don&rsquo;t need to use a local address during development <br \/>&nbsp;&nbsp;&nbsp;&nbsp; it isn&rsquo;t verified &#8211;&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;add key=&#8221;RelyingPartyRealm&#8221; value=&#8221;http:\/\/favorites.odata.org\/&#8221;\/&gt;<\/span><\/p>\n<p>&nbsp;With these values in place the next step is to enable the OAuthProtectionModule too.<\/p>\n<p><span style=\"font-family: Consolas\">&lt;system.webServer&gt;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; &lt;validation validateIntegratedModeConfiguration=&#8221;false&#8221; \/&gt;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; &lt;modules runAllManagedModulesForAllRequests=&#8221;true&#8221;&gt;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;add name=&#8221;OAuthProtectionModule&#8221; preCondition=&#8221;managedHandler&#8221;<br \/><\/span><span style=\"font-family: Consolas\">type=&#8221;OnlineFavoritesSite.OAuthProtectionModule&#8221;\/&gt;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; &lt;\/modules&gt;<br \/><\/span><span style=\"font-family: Consolas\">&lt;\/system.webServer&gt;<\/span><\/p>\n<p>&nbsp;With this in place any requests that include a correctly signed SWT in the Authorization header will have the HttpContext.Current.User set by the time you get into Data Services code.<\/p>\n<p>Now we just need a function to pull back a User (from the Database) based on the EmailAddress claim contained in the HttpContext.Current.User by calling GetOrCreateUserFromPrinciple(..).<\/p>\n<p>Per our business requirements this function automatically creates a new non-administrator user whenever a new EmailAddress is encountered. It talks to the database using the current ObjectContext which it accesses via DataService.CurrentDataSource. <\/p>\n<p><span style=\"font-family: Consolas\">public User GetOrCreateUserFromPrincipal(IPrincipal principal)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var emailAddress = GetEmailAddressFromPrincipal(principal);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; return GetOrCreateUserForEmail(emailAddress);<br \/><\/span><span style=\"font-family: Consolas\">}<\/span><\/p>\n<p><span style=\"font-family: Consolas\"><\/span><\/p>\n<p><span style=\"font-family: Consolas\">private string GetEmailAddressFromPrincipal(IPrincipal principal)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (principal == null) return null;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; else if ((principal is GenericPrincipal))<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return principal.Identity.Name;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; else if ((principal is IClaimsPrincipal))<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return GetEmailAddressFromClaim(principal as IClaimsPrincipal);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; else<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new InvalidOperationException(&#8220;Unexpected Principal type&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">}<\/span><\/p>\n<p><span style=\"font-family: Consolas\"><\/span><\/p>\n<p><span style=\"font-family: Consolas\">private string GetEmailAddressFromClaim(IClaimsPrincipal principal)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (principal == null) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new InvalidOperationException(&#8220;Need a claims principal to extract EmailAddress claim&#8221;);&nbsp;<\/p>\n<p><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var emailAddress = principal.Identities[0].Claims <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .Where(c =&gt; c.ClaimType == &#8220;http:\/\/schemas.xmlsoap.org\/ws\/2005\/05\/identity\/claims\/emailaddress&#8221;) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .Select(c =&gt; c.Value) <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .SingleOrDefault();<\/span><\/p>\n<p><span style=\"font-family: Consolas\">&nbsp;&nbsp; return emailAddress;<br \/><\/span><span style=\"font-family: Consolas\">} <br \/><\/span><\/p>\n<p><span style=\"font-family: Consolas\">private User GetOrCreateUserForEmail(string emailAddress)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (emailAddress == null)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new InvalidOperationException(&#8220;Need an emailaddress&#8221;);<\/span><\/p>\n<p><span style=\"font-family: Consolas\">&nbsp;&nbsp; var ctx = CurrentDataSource as FavoritesModelContainer;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var user = ctx.Users.WhereDbAndMemory(u =&gt; u.EmailAddress == emailAddress).SingleOrDefault();<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (user == null)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; user = new User<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Id = Guid.NewGuid(),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EmailAddress = emailAddress,<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CreatedDate = DateTime.Now,<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Administrator = false<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ctx.Users.AddObject(user);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; return user;<br \/><\/span><span style=\"font-family: Consolas\">} <br \/><\/span><\/p>\n<h3><b>Real World Note:<\/b><\/h3>\n<p>One thing that is interesting about this code is the call to WhereDbAndMemory(..) in GetOrCreateUserForEmail(..). Initially it was just a normal Where(..) call. <\/p>\n<p>But that introduced a pretty sinister bug. <\/p>\n<p>It turned out that often my query interceptors \/ change interceptors where being called multiple times in a single request and because this method creates a new user without saving it to the database every time it is called, it was creating more than one user for the same emailAddress. Which later failed the SingleOrDefault() test.<\/p>\n<p>The solution is to look for any unsaved Users in the ObjectContext, before creating another User. To do this I wrote a little extension method that allows you to query both the Database and unsaved changes in one go:<\/p>\n<p>&nbsp;<span style=\"font-family: Consolas\">public static IEnumerable&lt;T&gt; WhereDbAndMemory&lt;T&gt;( <br \/>&nbsp;&nbsp; this ObjectQuery&lt;T&gt; sequence, <br \/>&nbsp;&nbsp; Expression&lt;Func&lt;T, bool&gt;&gt; filter) where T: class<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var sequence1 = sequence.Where(filter).ToArray();<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var state = EntityState.Added | EntityState.Modified | EntityState.Unchanged;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var entries = sequence.Context.ObjectStateManager.GetObjectStateEntries(state);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var merged = sequence1.Concat(<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; entries.Select(e =&gt; e.Entity).OfType&lt;T&gt;().Where(filter.Compile())<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; ).Distinct();<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; return merged;<br \/><\/span><span style=\"font-family: Consolas\">} <br \/><\/span><\/p>\n<p>By using this function we can be sure to only ever create one User for a particular emailAddress.&nbsp;<\/p>\n<h2><b>Authorization<\/b><\/h2>\n<p>To implement our required business rules we need to create a series of Query and Change Interceptors that allow different users to do different things.<\/p>\n<p>Our first interceptor controls who can query users:<\/p>\n<p><span style=\"font-family: Consolas\">[QueryInterceptor(&#8220;Users&#8221;)]<br \/><\/span><span style=\"font-family: Consolas\">public Expression&lt;Func&lt;User, bool&gt;&gt; FilterUsers()<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (!HttpContext.Current.Request.IsAuthenticated)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; User user = GetOrCreateUserFromPrincipal(HttpContext.Current.User);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (user.Administrator)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return (u) =&gt; true;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; else<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">}<\/span><\/p>\n<p>Per our requirement this only allows authenticated Administrators to query Users. <\/p>\n<p>Next we need an interceptor that only allows administrators to modify a user:<\/p>\n<p><span style=\"font-family: Consolas\">[ChangeInterceptor(&#8220;Users&#8221;)]<br \/><\/span><span style=\"font-family: Consolas\">public void ChangeUser(User updated, UpdateOperations operations)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (!HttpContext.Current.Request.IsAuthenticated)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var user = GetOrCreateUserFromPrincipal(HttpContext.Current.User);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (!user.Administrator)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">}<\/span><\/p>\n<p>&nbsp;And now we restrict access to Favorites:<\/p>\n<p><span style=\"font-family: Consolas\">[QueryInterceptor(&#8220;Favorites&#8221;)]<br \/><\/span><span style=\"font-family: Consolas\">public Expression&lt;Func&lt;Favorite, bool&gt;&gt; FilterFavorites()<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (!HttpContext.Current.Request.IsAuthenticated)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">return (f) =&gt; f.Public == true;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">var user = GetOrCreateUserFromPrincipal(HttpContext.Current.User);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">var emailAddress = user.EmailAddress;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">if (user.Administrator)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">return (f) =&gt; true;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">else<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">return (f) =&gt; f.Public == true || f.User.EmailAddress == emailAddress;<br \/><\/span><span style=\"font-family: Consolas\">}<\/span><\/p>\n<p>&nbsp;As you can see administrators see everything, users see their favorites and everything public, and non-authenticated requests get to see just public favorites.<\/p>\n<p>Finally we control who can create, edit and delete favorites:<\/p>\n<p><span style=\"font-family: Consolas\">[ChangeInterceptor(&#8220;Favorites&#8221;)]<br \/><\/span><span style=\"font-family: Consolas\">public void ChangeFavorite(Favorite updated, UpdateOperations operations)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if (!HttpContext.Current.Request.IsAuthenticated)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ Get the current USER or create the current user&#8230;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var user = GetOrCreateUserFromPrincipal(HttpContext.Current.User);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ Handle Inserts&#8230;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; if ((operations &amp; UpdateOperations.Add) == UpdateOperations.Add)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ fill in the OwnerId, CreatedDate and Public properties<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; updated.OwnerId = user.Id;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; updated.CreatedDate = DateTime.Now;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; updated.Public = false;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; else if ((operations &amp; UpdateOperations.Change) == UpdateOperations.Change)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ Administrators can do whatever they want.<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (user.Administrator)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ We don&#8217;t trust the OwnerId on the wire (updated.OwnerId) because&nbsp;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ we should never do security checks based on something that the client<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ can modify!!!<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var original = GetOriginal(updated);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (original.OwnerId == user.Id)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ non-administrators can&#8217;t modify these values.<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; updated.OwnerId = user.Id;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; updated.CreatedDate = original.CreatedDate;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; updated.Public = original.Public;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp; }<\/span><\/p>\n<p><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ if we got here&#8230; they aren&#8217;t allowed to do anything!<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; }<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; else if ((operations &amp; UpdateOperations.Delete) == UpdateOperations.Delete)<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {&nbsp;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ in a delete operation you can&rsquo;t update the OwnerId &ndash; it is impossible&nbsp;<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ in the protocol, so it is safe to just check that. <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (updated.OwnerId != user.Id &amp;&amp; !user.Administrator)<br \/>&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new DataServiceException(401, &#8220;Permission Denied&#8221;);<br \/>&nbsp;&nbsp; <\/span><span style=\"font-family: Consolas\">}<br \/><\/span><span style=\"font-family: Consolas\">}<\/span><\/p>\n<p>Unauthenticated change requests are not allowed.<\/p>\n<p>For additions we always set the &lsquo;OwnedId&rsquo;, &lsquo;CreatedDate&rsquo; and &lsquo;Public&rsquo; properties overriding whatever was sent on the wire.<\/p>\n<p>For updates we allow administrators to make any changes, whereas owners can just edit their favorites, and they can&rsquo;t change the &lsquo;OwnerId&rsquo;, &lsquo;CreatedData&rsquo; or &lsquo;Public&rsquo; properties.&nbsp;&nbsp;<\/p>\n<p>It is also very important to understand that we have to get the original values before we check to see if someone is the owner of a particular favorite. We do this using this function that leverages some low level Entity Framework code:<\/p>\n<p><span style=\"font-family: Consolas\">private Favorite GetOriginal(Favorite updated)<br \/><\/span><span style=\"font-family: Consolas\">{<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ For MERGE based updates (which is the default) &#8216;updated&#8217; will be in the <br \/>&nbsp;&nbsp; \/\/ ObjectContext.ObjectStateManager.<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ For PUT based updates &#8216;updated&#8217; will NOT be in the<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ ObjectContext.ObjectStateManager, but it will contain a copy <br \/>&nbsp;&nbsp; \/\/ of the same entity.<\/p>\n<p><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ So to normalize we should find the ObjectStateEntry in the ObjectStateManager <br \/>&nbsp;&nbsp; \/\/ by EntityKey not by Entity.<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var entityKey = new EntityKey(&#8220;FavoritesModelContainer.Favorites&#8221;,&#8221;Id&#8221;, updated.Id);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var entry = CurrentDataSource.ObjectStateManager.GetObjectStateEntry(entityKey);<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; \/\/ Now we have the entity lets construct a copy with the original values.<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; var original = new Favorite<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; {<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Id = entry.OriginalValues.GetGuid(entry.OriginalValues.GetOrdinal(&#8220;Id&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CreatedDate = entry.OriginalValues.GetDateTime(entry.OriginalValues.GetOrdinal(&#8220;CreateDate&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Description = entry.OriginalValues.GetString(entry.OriginalValues.GetOrdinal(&#8220;Description&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Name = entry.OriginalValues.GetString(entry.OriginalValues.GetOrdinal(&#8220;Name&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OwnerId = entry.OriginalValues.GetGuid(entry.OriginalValues.GetOrdinal(&#8220;OwnerId&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Public = entry.OriginalValues.GetBoolean(entry.OriginalValues.GetOrdinal(&#8220;Public&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Uri = entry.OriginalValues.GetString(entry.OriginalValues.GetOrdinal(&#8220;Uri&#8221;)),<br \/><\/span><span style=\"font-family: Consolas\">&nbsp;&nbsp; };<\/span><\/p>\n<p><span style=\"font-family: Consolas\">&nbsp;&nbsp; return original;<br \/><\/span><span style=\"font-family: Consolas\">}<\/span>&nbsp;<\/p>\n<p>This constructs a copy of the unmodified entity setting all the properties from the original values in the ObjectStateEntry. While we don&rsquo;t actually need all the original values, I personally hate creating a function that only does half a job; it is a bug waiting to happen.<\/p>\n<p>Finally administrators can delete any favorites but users can only delete their own.<\/p>\n<h2><b>Summary<\/b><\/h2>\n<p>We&rsquo;ve gone from zero to hero in this example, all our business rules are implemented, our OData Service is protected using OAuth 2.0 and everything is working great. The only problem is we don&rsquo;t have a working client.<\/p>\n<p>So in the next post we&rsquo;ll create a Windows Phone 7 application for our OData service that knows how to authenticate.<\/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>In this post you will learn how to create an OData service that is protected using OAuth 2.0, which is the OData team&rsquo;s official recommendation in these scenarios: Delegation: In a delegation scenario a third party (generally an application) is granted access to a user&rsquo;s resources without the user disclosing their credentials (username and password) [&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,19,46,48,78],"class_list":["post-1033","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata","tag-authentication","tag-claims-based-security","tag-oauth","tag-odata","tag-wcf-data-services"],"acf":[],"blog_post_summary":"<p>In this post you will learn how to create an OData service that is protected using OAuth 2.0, which is the OData team&rsquo;s official recommendation in these scenarios: Delegation: In a delegation scenario a third party (generally an application) is granted access to a user&rsquo;s resources without the user disclosing their credentials (username and password) [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/1033","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=1033"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/1033\/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=1033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=1033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=1033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}