{"id":7174,"date":"2016-06-29T11:06:56","date_gmt":"2016-06-29T09:06:56","guid":{"rendered":"https:\/\/blog.redbaronofazure.com\/?p=7174"},"modified":"2016-06-29T12:34:55","modified_gmt":"2016-06-29T10:34:55","slug":"azure-ad-part-4-minimal-approach-to-authentication","status":"publish","type":"post","link":"https:\/\/blog.redbaronofazure.com\/?p=7174","title":{"rendered":"Azure AD part 4 &#8211; minimal approach to authentication"},"content":{"rendered":"<p>Following up on\u00a0my previous blog posts on Azure AD, I got the idea in my head to see what the minimal approach would be to implement Azure AD authentication in a DotNet based web application. Yes, there are componants you should use, like OWIN, ADAL, MSAL, etc, but how do they really work and just how big task is it to implement yourself if you really wanted &#8211; that was the question I wanted to answer.<\/p>\n<p><b>The simple web application<\/b><\/p>\n<p>I\u00a0created an empty WebForms application with just a Default.aspx page. I deliberately\u00a0choose WebForms since all modern examples of AAD auth are\u00a0MVC\/OWIN based. In the end I just added the Global.asax page, a page called Login.aspx and a C# class with just a few static methods and I was able to integrate with azure AD for authentication.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-1.png\"><img loading=\"lazy\" class=\"alignnone size-large wp-image-7175\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-1-1024x770.png\" alt=\"aaddotnet-website-1\" width=\"736\" height=\"553\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-1-1024x770.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-1-300x226.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-1-768x577.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-1.png 1345w\" sizes=\"(max-width: 736px) 100vw, 736px\" \/><\/a>When the user press the &#8220;Azure AD Login&#8221; menu item, it goes to the Login page with action=login.<\/p>\n<p><strong>Azure AD authentication via OAuth and OpenID<\/strong><\/p>\n<p>There are numerous flow charts out on the internet explaining the interaction between the browser, your web application and the Identity Provider (IDP) and to be honest, it can look a bit complex. Azure AD supports OAuth and OpenID and in Microsofts\u00a0documentation of OpenID and important detail about OpenID is explained<\/p>\n<p>&#8220;OpenID Connect 1.0 in Azure Active Directory (Azure AD) enables you to use the OAuth 2.0 protocol for single sign-on. OAuth 2.0 is an authorization protocol, but OpenID Connect extends OAuth 2.0 for use as an authentication protocol. A primary feature of the OpenID Connect protocol is that it returns an <em>id_token<\/em>, which is used to authenticate the user.&#8221; (see refs)<\/p>\n<p>So, by redirecting from my web application to Azure AD, asking for an OAuth authorization AND asking for a response_type=id_token, we can do a authorization\/authentication in one call.<\/p>\n<p><strong>Login\u00a0Sequence<\/strong><\/p>\n<p>By using Fiddler, we can get a clear understanding of what is happening here. Clicking on the menu item in the browser calls the Login.aspx page (frame 31) which responds with a redirection url\u00a0to\u00a0Azure AD. The browser redirects to Azure AD in frame 33 asking for an authentication. In the query parameter, we pass identification of the web application (redirect_uri, clientID and clientSecret) which\u00a0are items we have registered in Azure AD.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-1.png\"><img loading=\"lazy\" class=\"alignnone size-large wp-image-7178\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-1-1024x519.png\" alt=\"aaddotnet-fiddler-1\" width=\"736\" height=\"373\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-1-1024x519.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-1-300x152.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-1-768x389.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-1.png 1273w\" sizes=\"(max-width: 736px) 100vw, 736px\" \/><\/a><\/p>\n<p>The important part is responseType=id_token, which tells Azure AD to return a JWT Token on a successfull authentication. Azure AD responds with yet a redirection request to the browser and this time to the destination we supplied in the callbackURL parameter. You can see the browser making this call in frame 35.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-2.png\"><img loading=\"lazy\" class=\"alignnone size-large wp-image-7179\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-2-1024x303.png\" alt=\"aaddotnet-fiddler-2\" width=\"736\" height=\"218\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-2-1024x303.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-2-300x89.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-2-768x227.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-fiddler-2.png 1309w\" sizes=\"(max-width: 736px) 100vw, 736px\" \/><\/a><\/p>\n<p>Code that handles clicking on the login link and that returns the redirection url to Azure AD<\/p>\n<pre class=\"theme:vs2012-black lang:c# decode:true\">            string nonce = System.Guid.NewGuid().ToString();\r\n            string authUrl = \"https:\/\/login.microsoftonline.com\/common\/oauth2\/authorize\";\r\n            string clientID = ConfigurationManager.AppSettings[\"aad.clientid\"];\r\n            if (string.IsNullOrEmpty(clientID))\r\n                throw new ArgumentNullException(\"clientID\", \"ClientID must be specified in Web.Config as aad.clientid under AppSettings\");\r\n            string clientSecret = ConfigurationManager.AppSettings[\"aad.clientsecret\"];\r\n            if (string.IsNullOrEmpty(clientSecret))\r\n                throw new ArgumentNullException(\"clientSecret\", \"clientSecret must be specified in Web.Config as aad.clientsecret under AppSettings\");\r\n            string appIdUri = ConfigurationManager.AppSettings[\"aad.appiduri\"];\r\n            if (string.IsNullOrEmpty(appIdUri))\r\n                throw new ArgumentNullException(\"appIdUri\", \"clientSecret must be specified in Web.Config as aad.appiduri under AppSettings\");\r\n            redirectUri = GetRedirectUrl(Request, redirectUri);\r\n            if (string.IsNullOrEmpty(redirectUri))\r\n                throw new ArgumentNullException(\"redirectUri\", \"redirectUri must be specified in Web.Config as aad.appiduri under AppSettings\");\r\n            \/\/ build url for AAD auth and redirect to ourself \r\n            StringBuilder sb = new StringBuilder();\r\n            sb.AppendFormat(\"{0}?\", authUrl);\r\n            sb.AppendFormat(\"redirect_uri={0}\", Uri.EscapeDataString(redirectUri));\r\n            sb.AppendFormat(\"&amp;nonce={0}\", nonce);\r\n            sb.AppendFormat(\"&amp;authorizationURL={0}\", Uri.EscapeDataString(authUrl));\r\n            sb.AppendFormat(\"&amp;callbackURL={0}\", Uri.EscapeDataString(redirectUri));\r\n            sb.AppendFormat(\"&amp;clientID={0}\", clientID);\r\n            sb.AppendFormat(\"&amp;clientSecret={0}\", Uri.EscapeDataString(clientSecret));\r\n            sb.AppendFormat(\"&amp;identifierField=openid_identifier\");\r\n            sb.AppendFormat(\"&amp;oidcIssuer={0}\", Uri.EscapeDataString(\"https:\/\/sts.windows.net\/{tenantid}\/\"));\r\n            sb.AppendFormat(\"&amp;responseType=id_token\");\r\n            sb.AppendFormat(\"&amp;revocationURL={0}\", Uri.EscapeDataString(\"https:\/\/login.microsoftonline.com\/common\/oauth2\/logout\"));\r\n            sb.AppendFormat(\"&amp;scopeSeparator=%20\");\r\n            sb.AppendFormat(\"&amp;tokenInfoURL=\");\r\n            sb.AppendFormat(\"&amp;tokenURL={0}\", Uri.EscapeDataString(\"https:\/\/login.microsoftonline.com\/common\/oauth2\/token\"));\r\n            sb.AppendFormat(\"&amp;userInfoURL={0}\", Uri.EscapeDataString(\"https:\/\/login.microsoftonline.com\/common\/openid\/userinfo\"));\r\n            sb.AppendFormat(\"&amp;response_mode=form_post\");\r\n            sb.AppendFormat(\"&amp;response_type=id_token\");\r\n            sb.AppendFormat(\"&amp;scope=openid\");\r\n            sb.AppendFormat(\"&amp;client_id={0}\", clientID);\r\n            sb.AppendFormat(\"&amp;state={0}\", nonce);\r\n            \/\/ redirect to auth via AAD (and then redirect back to ourself)\r\n            Response.Redirect(sb.ToString(), true);\r\n<\/pre>\n<p><strong>We&#8217;re authenticated, now what?<\/strong><\/p>\n<p>The callback to Login.aspx then has to decode the JWT token and covert them to claims and setting up a claims identity. But that is not all. In order keep this on a session level we need to create a cookie on the callback processing and for each subsequent request, we need to read this cookie and recreate\/reapply the claims identity. Simple, right?<\/p>\n<p><strong>Step 1 &#8211; Handling the Callback<\/strong><\/p>\n<p>First, we shouldn&#8217;t do anything if we are already authenticated or ir the callback is missing the id_token in it&#8217;s post body. Second, decode the JWT token into JSON and create a ClaimsPrincipal. Third, create the FormsAuthenticationTicket where we store the JWT token in the UserData section so that we always have it available. Fourth, create the cookie using the well known name ASPXAUTH (FormsCookieName) and redirect ourselves to wherever we should go after authentication is complete<\/p>\n<pre class=\"theme:vs2012-black lang:c# decode:true \">            \/\/ we shouln't already be Auth'd and we need the \"id_token\" part in the body\r\n            if (Request.IsAuthenticated) return;\r\n            if (!Request.Form.AllKeys.Contains(\"id_token\")) return;\r\n            \r\n            \/\/ decode shit\r\n            string value = Request.Form.Get(\"id_token\");\r\n            JObject id_token = JwtDecode(value);\r\n            \/\/ UserPrincipalNme, ie a fancy word for the original e-mail address you have in ActiveDirectory\r\n            string upn = id_token.GetValue(\"upn\").ToString();\r\n            DateTime expireTime = GetExpireTime(id_token);\r\n            SetUserPrincipal(id_token);\r\n\r\n            \/\/ create the cookie and store the JWT token in the UserData attrribute so we can pick it up \r\n            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, upn, DateTime.UtcNow, expireTime, false, id_token.ToString(), FormsAuthentication.FormsCookiePath);\r\n            string encryptedCookie = FormsAuthentication.Encrypt(ticket);\r\n            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedCookie);\r\n            cookie.Expires = expireTime;\r\n            Response.Cookies.Add(cookie);\r\n\r\n            \/\/ redirect to ourself\r\n            redirectUri = GetRedirectUrl(Request, redirectUri);\r\n            Response.Redirect(redirectUri, true);\r\n<\/pre>\n<p>Creating the ClaimsPrincipal is straight forward. Setting the HttpContext.Current.User makes the boolean flag Request.IsAuthentication flip from false to true, which is important since that is how we check in code if we are dealing with an anonymous or authenticated user. Setting Thread.CurrentPrincipal makes the claims available since the static method ClaimsPrincipal.Current actually uses this value.<\/p>\n<pre class=\"theme:vs2012-black lang:c# decode:true\">private static void SetUserPrincipal( JObject id_token )\r\n{\r\n    string upn = id_token.GetValue(\"upn\").ToString();\r\n    List&lt;Claim&gt; claims = new List&lt;Claim&gt;\r\n                {\r\n                      new Claim(ClaimTypes.Email, upn )\r\n                    , new Claim(ClaimTypes.Upn, upn )\r\n                    , new Claim( \"http:\/\/schemas.microsoft.com\/identity\/claims\/objectidentifier\", id_token.GetValue(\"oid\").ToString() )\r\n                    , new Claim(ClaimTypes.Surname, id_token.GetValue(\"family_name\").ToString() )\r\n                    , new Claim(ClaimTypes.GivenName, id_token.GetValue(\"given_name\").ToString() )\r\n                    , new Claim(ClaimTypes.Name, id_token.GetValue(\"unique_name\").ToString() )\r\n                    , new Claim(\"name\", id_token.GetValue(\"name\").ToString() )\r\n                    , new Claim(\"iss\", id_token.GetValue(\"iss\").ToString() )\r\n                    , new Claim(\"nbf\", id_token.GetValue(\"nbf\").ToString() )\r\n                    , new Claim(\"exp\", id_token.GetValue(\"exp\").ToString() )\r\n                    , new Claim(\"aud\", id_token.GetValue(\"aud\").ToString() )\r\n                    , new Claim(ClaimTypes.NameIdentifier, id_token.GetValue(\"sub\").ToString() )\r\n                    , new Claim(\"ipaddr\", id_token.GetValue(\"ipaddr\").ToString() )\r\n                    , new Claim(\"http:\/\/schemas.microsoft.com\/identity\/claims\/tenantid\", id_token.GetValue(\"tid\").ToString() )\r\n                    , new Claim(\"ver\", id_token.GetValue(\"ver\").ToString() )\r\n                };\r\n    ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, \"Cookies\");\r\n    ClaimsPrincipal principal = new ClaimsPrincipal(claimsIdentity);\r\n    HttpContext.Current.User = principal;\r\n    Thread.CurrentPrincipal = principal; \/\/ updates ClaimsPrincipal.Current\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-cookie-1.png\"><img loading=\"lazy\" class=\"alignnone size-large wp-image-7180\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-cookie-1-1024x270.png\" alt=\"aaddotnet-cookie-1\" width=\"736\" height=\"194\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-cookie-1-1024x270.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-cookie-1-300x79.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-cookie-1-768x202.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-cookie-1.png 1203w\" sizes=\"(max-width: 736px) 100vw, 736px\" \/><\/a><\/p>\n<p><strong>Step 2 &#8211; Reapplying the cookie on each subsequent Request<\/strong><\/p>\n<p>If we do nothing on the following request, the flag Request.IsAuthenticated will be false, so we need to recreate the ClaimsPrincipal and reapply on each subsequent request. This is the only way it can work if your web applicaiton is hosted on multiple servers.<\/p>\n<p>In Global.asax there is a method that is there just for this and it&#8217;s called AuthenticateRequest<\/p>\n<pre class=\"theme:vs2012-black lang:c# decode:true \">public class Global : System.Web.HttpApplication\r\n{\r\n    protected void Application_AuthenticateRequest(object sender, EventArgs e)\r\n    {\r\n        Tiny.AzureAD.OAuthHandler.GlobalApplication_AuthenticateRequest(Request, Response);\r\n    }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<pre class=\"theme:vs2012-black lang:c# decode:true \">public static void GlobalApplication_AuthenticateRequest(HttpRequest Request, HttpResponse Response)\r\n{\r\n    \/\/ if not already auth'd and we have the aspnet auth cookie, hook up the principal if cookie hasn't expired\r\n    if (!Request.IsAuthenticated &amp;&amp; Request.Cookies.AllKeys.Contains(FormsAuthentication.FormsCookieName))\r\n    {\r\n        HttpCookie cookie = Request.Cookies.Get(FormsAuthentication.FormsCookieName);\r\n        if (cookie != null)\r\n        {\r\n            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);\r\n            var id_token = JObject.Parse(ticket.UserData);\r\n            DateTime expireTime = GetExpireTime(id_token);\r\n            if (DateTime.UtcNow &lt; expireTime)\r\n            {\r\n                SetUserPrincipal(id_token);\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>The end result is a web page that behaves just like you expect and that can show the claim values we created.<\/p>\n<p><a href=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-3.png\"><img loading=\"lazy\" class=\"alignnone size-large wp-image-7177\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-3-1024x770.png\" alt=\"aaddotnet-website-3\" width=\"736\" height=\"553\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-3-1024x770.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-3-300x226.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-3-768x577.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2016\/06\/aaddotnet-website-3.png 1345w\" sizes=\"(max-width: 736px) 100vw, 736px\" \/><\/a><\/p>\n<p><strong>Logoff<\/strong><\/p>\n<p>Logging off is basically the same process but simpler. It&#8217;s a redirect to Azure AD again asking it to logoff and at the same time making sure the cookie we have is set to expired.<\/p>\n<p><strong>Summary<\/strong><\/p>\n<p>This excersie is about showing you that you can roll your own implementation of integrating Azure AD authentication in your web application. In doing so I hope I gave you an understanding of how easy it is and how it really works behind the covers. However, with Identity you should NEVER roll your own solutions and should ALWAYS use componants that are tested and maintained by bigger players.<\/p>\n<p><strong>References<\/strong><\/p>\n<p>OpenID 1.0 Connect<br \/>\n<a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/azure\/dn645541.aspx\">https:\/\/msdn.microsoft.com\/en-us\/library\/azure\/dn645541.aspx<\/a><\/p>\n<p>OpenID 1.0 Specifications<br \/>\n<a href=\"http:\/\/openid.net\/developers\/specs\/\">http:\/\/openid.net\/developers\/specs\/<\/a><\/p>\n<p>Authorize webapps with OAuth and Azure AD<br \/>\n<a href=\"https:\/\/azure.microsoft.com\/en-us\/documentation\/articles\/active-directory-protocols-oauth-code\/\">https:\/\/azure.microsoft.com\/en-us\/documentation\/articles\/active-directory-protocols-oauth-code\/<\/a><\/p>\n<p>Azure AD Developer&#8217;s Guide<br \/>\n<a href=\"https:\/\/azure.microsoft.com\/en-us\/documentation\/articles\/active-directory-developers-guide\/\">https:\/\/azure.microsoft.com\/en-us\/documentation\/articles\/active-directory-developers-guide\/<\/a><\/p>\n<p><strong>Sources<\/strong><\/p>\n<p>Available on github<br \/>\n<a href=\"https:\/\/github.com\/cljung\/azwebaadtiny\">https:\/\/github.com\/cljung\/azwebaadtiny<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Following up on\u00a0my previous blog posts on Azure AD, I got the idea in my head to see what the minimal approach would be to implement Azure AD authentication in a DotNet based web application. Yes, there are componants you should use, like OWIN, ADAL, MSAL, etc, but how do they really work and just [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[392],"tags":[402,403],"_links":{"self":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7174"}],"collection":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=7174"}],"version-history":[{"count":12,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7174\/revisions"}],"predecessor-version":[{"id":7192,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7174\/revisions\/7192"}],"wp:attachment":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7174"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7174"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}