{"id":290,"date":"2010-07-09T19:11:41","date_gmt":"2010-07-09T17:11:41","guid":{"rendered":"http:\/\/kinsey.no\/blog\/?p=290"},"modified":"2010-07-09T19:11:41","modified_gmt":"2010-07-09T17:11:41","slug":"how-to-set-up-an-ad-backed-openid-provider-without-direct-communication","status":"publish","type":"post","link":"https:\/\/kinsey.no\/blog\/index.php\/2010\/07\/09\/how-to-set-up-an-ad-backed-openid-provider-without-direct-communication\/","title":{"rendered":"How to set up an AD backed OpenID Provider without direct communication"},"content":{"rendered":"<p>I&#8217;ve currently got a project where I need to provide an <a href=\"http:\/\/openid.net\/specs\/openid-authentication-2_0.html#terminology\" target=\"_blank\">OpenID Provider<\/a> (OP) to authenticate users using Active Directory (AD), something that shouldn&#8217;t be to much of a hassle.<br \/>\n<b>But there is a catch:<\/b> the OP needs to be <i>outside<\/i> the firewall in order to talk to the <a href=\"http:\/\/openid.net\/specs\/openid-authentication-2_0.html#terminology\" target=\"_blank\">Relying Parties<\/a> (RP) and is unable to communicate directly with AD. <b>So what can you do?<\/b><br \/>\n<!--more--><br \/>\n<img loading=\"lazy\" class=\"size-medium wp-image-292 alignright\" title=\"easyxdm-openid\" src=\"http:\/\/kinsey.no\/blog\/wp-content\/uploads\/2010\/07\/easyxdm-openid-300x225.png\" alt=\"\" width=\"300\" height=\"225\" srcset=\"https:\/\/kinsey.no\/blog\/wp-content\/uploads\/2010\/07\/easyxdm-openid-300x225.png 300w, https:\/\/kinsey.no\/blog\/wp-content\/uploads\/2010\/07\/easyxdm-openid.png 695w\" sizes=\"(max-width: 300px) 100vw, 300px\" \/>Well, in this case the OP is only going to be used by users that are sitting <i>behind<\/i> the firewall, and who have access to both internal resources and external resources over port 80, and what this means is that we can use the client (the browser) to relay messages between the OP and the asserting party. In order to make the communication going via the browser trusted, we can use a challenge combined with\u00a0symmetric encryption to verify authenticity.<\/p>\n<p>This means that the chain of trust needed for the identity assertion looks like the following<\/p>\n<ul>\n<li>The RP trust the assertion from the OP\n<ul>\n<li>The RP and the OP shares can communicate directly and uses a shared secret to verify the authenticity of the message passed through the browser<\/li>\n<\/ul>\n<\/li>\n<li>The RP trusts the assertion from the Internal Webserver (IW)\n<ul>\n<li>The RP and the IW has a pre-shared encryption key that together with a challenge serves to verify the authenticity of the messages passed through the browser<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><b>In the following code <a href=\"dotnetopenauth.net\/\" target=\"_blank\">DotNetOpenAuth <\/a>is used to provide support for OpenID, and <a href=\"http:\/\/easyxdm.net\/\" target=\"_blank\">easyXDM<\/a> is used to provide the Cross-Document Communication.<\/b><br \/>\nCodewise, it all starts once the OpenID request hits the OP, and at this point we only store away the request and render a blank page containing the following JavaScript<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\/\/ server.aspx\r\n\/\/ This is called by ASP.NET Ajax once the page is ready\r\nfunction pageLoad() {\r\n    \/\/ Set up a new easyXDM.Rpc object\r\n    var rpc = new easyXDM.Rpc({\r\n        remote: &quot;http:\/\/localhost:55192\/Endpoint.aspx&quot;\r\n    }, {\r\n        remote: {\r\n            authenticate: {}\r\n        }\r\n    });\r\n\r\n    \/\/ Retrieve a challenge from the server \r\n    PageMethods.GetChallenge(function (challenge) {\r\n        \/\/ Relay the challenge to the internal webserver\r\n        rpc.authenticate(challenge, function (response) {\r\n            \/\/ Relay the response back to the server\r\n            PageMethods.VerifyResponse(response, function (verified) {\r\n                \/\/ If the response was verified, we redirect to the page that will complete the OpenID assertion\r\n                if (verified) {\r\n                    location.href = &quot;complete.aspx&quot;;\r\n                } else {\r\n                    alert(&quot;not authenticated&quot;);\r\n                }\r\n            });\r\n        });\r\n    });\r\n}\r\n<\/pre>\n<p>As you can see this uses PageMethods to talk to the server side code for retrieving the challenge, and for delivering the response<\/p>\n<pre class=\"brush: vb; title: ; notranslate\" title=\"\">\r\n'server.aspx\r\n&lt;WebMethod()&gt; _\r\nPublic Shared Function GetChallenge() As Utilities.Challenge\r\n    ' This method is called to create a challenge, and to get access to the claimed identifier that the internal webserver will have to verify\r\n    ' Create and store away a challenge\r\n    Dim challenge As New Utilities.Challenge\r\n    challenge.Guid = System.Guid.NewGuid().ToString()\r\n    HttpContext.Current.Session(&quot;guid&quot;) = challenge.Guid\r\n\r\n    If providerEndpoint.PendingAuthenticationRequest IsNot Nothing Then\r\n        challenge.Identifier = providerEndpoint.PendingAuthenticationRequest.ClaimedIdentifier.OriginalString\r\n    End If\r\n    ' Return a Plain Old CRL Object (POCO). This will be serialized into JSON\r\n    Return challenge\r\nEnd Function\r\n\r\n&lt;WebMethod()&gt; _\r\nPublic Shared Function VerifyResponse(ByVal response As String) As Boolean\r\n    ' This method will be called after the response returns from the internal webserver\r\n    If providerEndpoint.PendingAuthenticationRequest IsNot Nothing Then\r\n        Dim enc = New Utilities.SymmetricEncryption(&quot;my secret&quot;)\r\n        ' Encrypt the claimed identity and the stored challenge, and see if it matches the response from the internal server\r\n        If enc.Encrypt(providerEndpoint.PendingAuthenticationRequest.ClaimedIdentifier.OriginalString &amp; &quot;_&quot; &amp; HttpContext.Current.Session(&quot;guid&quot;)) = response Then\r\n            ' If it matches we issue an authentication token\r\n            FormsAuthentication.SetAuthCookie(providerEndpoint.PendingAuthenticationRequest.ClaimedIdentifier, False)\r\n            Return True\r\n        End If\r\n    End If\r\n    Return False\r\nEnd Function\r\n<\/pre>\n<p>On the internal server the setup is similar, we accept the claimed identity, verify it against the identity of the authenticated user, and return a signed response.<br \/>\nHere we expose a method whose only purpose is to relay the challenge back to the serverside code<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\/\/ Endpoint.aspx\r\n\/\/ This is called by ASP.NET Ajax once the page is ready\r\nfunction pageLoad() {\r\n    \/\/ Set up a new easyXDM.Rpc object\r\n    var rpc = new easyXDM.Rpc({}, {\r\n        local: {\r\n            authenticate: function (challenge, fn) {\r\n                \/\/ Relay the challenge to the serverside code\r\n                PageMethods.Authenticate(challenge, function (response) {\r\n                    \/\/ Relay the response back to the OP\r\n                    fn(response);\r\n                });\r\n            }\r\n        }\r\n    });\r\n}\r\n<\/pre>\n<p>Here we extract the username from the claimed identifier and compare it to the known one. If it matches we return an encrypted response <\/p>\n<pre class=\"brush: vb; title: ; notranslate\" title=\"\">\r\n'Endpoint.aspx\r\n&lt;WebMethod()&gt; _\r\nPublic Shared Function Authenticate(ByVal challenge As Utilities.Challenge) As String\r\n    ' This is called to check wether the claimed identifier matches the known one\r\n    Dim claimedUserName As String = New Uri(challenge.Identifier).Query.Substring(4)\r\n\r\n    If HttpContext.Current.User.Identity.IsAuthenticated Then\r\n        Dim username As String = HttpContext.Current.User.Identity.Name\r\n        username = username.Substring(username.IndexOf(&quot;\\&quot;) + 1)\r\n        If username = claimedUserName Then\r\n            ' If it matches we encrypt the identifier and the challenge, and return the result as the response\r\n            Dim enc = New Utilities.SymmetricEncryption(&quot;my secret&quot;)\r\n            Return enc.Encrypt(challenge.Identifier &amp; &quot;_&quot; &amp; challenge.Guid)\r\n        End If\r\n    End If\r\n\r\n    Return String.Empty\r\nEnd Function\r\n<\/pre>\n<p>One of the interesting things here is how the instances of <code>Utilities.Challenge<\/code> is serialized and deserialized going from the OP&#8217;s server side code to the client side code, and again serialized and deserialized going from the internal webservers client side code to the internal webservers server side code. Like magic right?<\/p>\n<p>(Of course, we could easily have used a couple of redirects instead of the server-client-client-server communication, but that wouldn&#8217;t have been as fun now would it \ud83d\ude42<\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve currently got a project where I need to provide an OpenID Provider (OP) to authenticate users using Active Directory (AD), something that shouldn&#8217;t be to much of a hassle. But there is a catch: the OP needs to be outside the firewall in order to talk to the Relying Parties (RP) and is unable [&hellip;]<!-- AddThis Advanced Settings generic via filter on get_the_excerpt --><!-- AddThis Share Buttons generic via filter on get_the_excerpt --><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[56,34],"tags":[64,65,66,22],"_links":{"self":[{"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/posts\/290"}],"collection":[{"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=290"}],"version-history":[{"count":21,"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/posts\/290\/revisions"}],"predecessor-version":[{"id":312,"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/posts\/290\/revisions\/312"}],"wp:attachment":[{"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=290"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=290"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsey.no\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=290"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}