{"id":7720,"date":"2021-04-26T12:31:17","date_gmt":"2021-04-26T10:31:17","guid":{"rendered":"https:\/\/blog.redbaronofazure.com\/?p=7720"},"modified":"2021-05-27T12:46:32","modified_gmt":"2021-05-27T10:46:32","slug":"building-an-asp-net-core-webapp-that-authenticates-with-saml-to-azure-ad-b2c","status":"publish","type":"post","link":"https:\/\/blog.redbaronofazure.com\/?p=7720","title":{"rendered":"Building an ASP.Net Core webapp that authenticates with SAML to Azure AD B2C"},"content":{"rendered":"\n<p>Most new application today are built using OIDC\/OAuth protocols, and if you need a ASP.Net sample for that, you have plenty to choose from when working with Azure AD B2C as your identity provider. But if you have to work with the SAML protocol and B2C, the number of samples grow small &#8211; very small. In fact, there are more Java samples available than ASP.Net when working with SAML and B2C. This  blog post is an attempt to close that gap and give you one.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"995\" height=\"653\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-not-logged-in.png\" alt=\"\" class=\"wp-image-7731\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-not-logged-in.png 995w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-not-logged-in-300x197.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-not-logged-in-768x504.png 768w\" sizes=\"(max-width: 995px) 100vw, 995px\" \/><\/figure>\n\n\n\n<h3>Configure an Azure AD B2C SAML policy<\/h3>\n\n\n\n<p>Setting up a SAML signin policy in Azure AD B2C is a series of multiple steps. First, you need have a B2C instance. If you don&#8217;t just create one. It won&#8217;t cost anything until you go above 50K monthly active users signing in. Second you need to configure Identity Experience Framework to enable custom policies as SAML is only supported by B2C that way. Third, you need to create a B2C custom policy that supports the SAML protocol. In my accompanying <a href=\"https:\/\/github.com\/cljung\/b2c-saml-webapp\" target=\"_blank\" rel=\"noreferrer noopener\">github repo<\/a>, I&#8217;ll provide you with a SAML B2C custom policy.<\/p>\n\n\n\n<p>Documentation on getting started with B2C Custom Policy can be found <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory-b2c\/tutorial-create-user-flows?pivots=b2c-custom-policy&amp;tabs=applications#custom-policy-starter-pack\" target=\"_blank\">here<\/a> and documentation on how to create a SAML based B2C Custom Policy can be found <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory-b2c\/saml-service-provider?tabs=windows&amp;pivots=b2c-custom-policy\" target=\"_blank\">here<\/a>.<\/p>\n\n\n\n<h3>Creating the ASP.Net Core webapp<\/h3>\n\n\n\n<p>I&#8217;ve used Visual Studio 2019 to create the ASP.Net Core webapp from scratch and these are the steps.<\/p>\n\n\n\n<h4>1. Create a new Visual Studio project <\/h4>\n\n\n\n<p>Create a new C# Web project and choose template ASP.NET Code Web MVC. In the next page, select aspnet core 3.1 as platform and Authentication Type as Microsoft Identity Platform.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"1024\" height=\"680\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-new-project-2-1024x680.png\" alt=\"\" class=\"wp-image-7724\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-new-project-2-1024x680.png 1024w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-new-project-2-300x199.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-new-project-2-768x510.png 768w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-new-project-2.png 1280w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h4>2. Add Sustainsys SAML nuget package<\/h4>\n\n\n\n<p>We need a library that understands SAML and one of the more popular ones out there that supports aspnet core is <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/Sustainsys\/Saml2\" target=\"_blank\">Sustainsys.Saml2<\/a>.  In Visual Studio, add nuget package <em>Sustainsys.Saml2.AspNetCore2<\/em>. The most recent version at the time of writing this was 2.8.0.<\/p>\n\n\n\n<h4>3. Edit Startup.cs<\/h4>\n\n\n\n<p><em>Startup.cs<\/em> needs some editing to make the webapp be a SAML Service Provider and use Azure AD B2C as its IDP. The ConfigureServices method should look like below (you have the full code in my github repo).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp line-numbers\">public void ConfigureServices(IServiceCollection services)\n{\n    \/\/ we need to associate SHA1\/SHA256 with the long web-based names for Sustainsys.Saml2 to work\n    System.Security.Cryptography.CryptoConfig.AddAlgorithm(typeof(RsaPkCs1Sha256SignatureDescription), System.Security.Cryptography.Xml.SignedXml.XmlDsigRSASHA256Url);\n    System.Security.Cryptography.CryptoConfig.AddAlgorithm(typeof(RsaPkCs1Sha1SignatureDescription), System.Security.Cryptography.Xml.SignedXml.XmlDsigRSASHA1Url);\n\n    services.AddAuthentication(sharedOptions =>\n    {\n        sharedOptions.DefaultScheme = Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme;\n        sharedOptions.DefaultSignInScheme = Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme;\n        sharedOptions.DefaultChallengeScheme = Sustainsys.Saml2.AspNetCore2.Saml2Defaults.Scheme;\n    })\n    .AddSaml2(options =>\n    {\n        options.SPOptions = new Sustainsys.Saml2.Configuration.SPOptions()\n        {\n            AuthenticateRequestSigningBehavior = Sustainsys.Saml2.Configuration.SigningBehavior.Never,\n            EntityId = new Sustainsys.Saml2.Metadata.EntityId(Configuration.GetValue&lt;string>(\"Saml2:EntityId\")),\n            MinIncomingSigningAlgorithm = Configuration.GetValue&lt;string>(\"Saml2:MinIncomingSigningAlgorithm\")\n        };\n\n        \/\/ We need to use a cert for Sustainsys.Saml2 to work with logout, so we borrow their sample cert\n        \/\/ https:\/\/github.com\/Sustainsys\/Saml2\/blob\/develop\/Samples\/SampleAspNetCore2ApplicationNETFramework\/Sustainsys.Saml2.Tests.pfx\n        string certFile = string.Format(\"{0}\\\\{1}\", System.IO.Directory.GetCurrentDirectory(), Configuration.GetValue&lt;string>(\"Saml2:cert\"));\n        options.SPOptions.ServiceCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate2(certFile));\n\n        \/\/ The Azure AD B2C Identity Provider we use\n        options.IdentityProviders.Add(\n            new Sustainsys.Saml2.IdentityProvider(\n            new Sustainsys.Saml2.Metadata.EntityId(Configuration.GetValue&lt;string>(\"Saml2:IdpEntityId\")), options.SPOptions)\n            {\n                MetadataLocation = Configuration.GetValue&lt;string>(\"Saml2:IdpMetadata\"),\n                LoadMetadata = true\n            });\n    })\n    .AddCookie();\n\n    services.AddControllersWithViews(options =>\n    {\n        var policy = new AuthorizationPolicyBuilder()\n            .RequireAuthenticatedUser()\n            .Build();\n        options.Filters.Add(new AuthorizeFilter(policy));\n    });\n    services.AddRazorPages();\n}\n<\/code><\/pre>\n\n\n\n<p>In order for Sustainsys.Saml2 to work with B2C, it needs a certificate for the webapp. You could generate a self issued cert, but you can save you some work by downloading the sample PFX cert that Sustainsys have <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/Sustainsys\/Saml2\/blob\/develop\/Samples\/SampleAspNetCore2ApplicationNETFramework\/Sustainsys.Saml2.Tests.pfx\" target=\"_blank\">here<\/a>. Download it and place it in the project root folder.<\/p>\n\n\n\n<p>The Startup.cs file also has some additional code for making the SHA1 and SHA256 algorithms available  by the long URI names that SAML uses. I was a bit surprised that this was needed. Check the github repo for this and paste it at the end of Startup.cs.<\/p>\n\n\n\n<h4>4. Update appsettings.json with new configuration<\/h4>\n\n\n\n<p>Startup.cs uses configuration settings that are stored in appsettings.json. The EntityId is the name to which your webapp will be known to B2C when we do the App Registration. It is the OIDC equivalend of the client_id. The IdpEntityId is the B2C policy and the IdpMetadata is the endpoint for where your webapp can get SAML metadata for B2C.   <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript line-numbers\">{\n  \"Saml2\": {\n    \"cert\": \"Sustainsys.Saml2.Tests.pfx\",\n    \"EntityId\": \"https:\/\/localhost:5001\/Saml2\",\n    \"MinIncomingSigningAlgorithm\": \"http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1\",\n    \"IdpEntityId\": \"https:\/\/yourtenant.b2clogin.com\/yourtenant.onmicrosoft.com\/B2C_1A_SAML_signup_signin\",\n    \"IdpMetadata\": \"https:\/\/yourtenant.b2clogin.com\/yourtenant.onmicrosoft.com\/B2C_1A_SAML_signup_signin\/samlp\/metadata\"\n  },\n  \"Logging\": {\n    ...\n}\n<\/code><\/pre>\n\n\n\n<p>Note that https:\/\/localhost:5001\/Saml2 is for when I run it as a standalone process. If you prefer IISExpress, you will get a different port number.<\/p>\n\n\n\n<h4>5. Edit HomeController.cs<\/h4>\n\n\n\n<p>The generated HomeController.cs sets the [Authorize] attribute on the entire class, which makes the app require signin when you first hit the index page. Therefor, remove the [Authorize] attribute for the class and add [AllowAnonymous] for the Index and Privacy method. This launches the app without the need to signin and you can then press the Signin button.<\/p>\n\n\n\n<h4>6. Add AccountController.cs<\/h4>\n\n\n\n<p>We need a new controller that will handle the actions for sign in and sign out, so right click and add a new controller named AccountController.cs (use the MVC empty template). Copy the code from the github repo and paste over what was generated.<\/p>\n\n\n\n<p>In the SignOut method, you need to add the SessionIndex and the LogoutNameIdentifier, which are two client side claims that the Sustainsys component added, for the sign out to work. If you don&#8217;t set these, the Sustainsys component will fail as it doesn&#8217;t know which session to sign out.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp line-numbers\">public IActionResult SignOut()\n{\n    _logger.LogInformation(\"SignOut()\");\n    var authProps = new AuthenticationProperties\n    {\n        RedirectUri = Url.Action(nameof(Index), \"Home\", values: null, protocol: Request.Scheme)\n    };\n    \/\/ you need these two in order for Sustainsys.Saml2 to successfully sign out\n    AddAuthenticationPropertiesClaim(authProps, \"\/SessionIndex\");\n    AddAuthenticationPropertiesClaim(authProps, \"\/LogoutNameIdentifier\");\n    return SignOut(authProps, CookieAuthenticationDefaults.AuthenticationScheme, Sustainsys.Saml2.AspNetCore2.Saml2Defaults.Scheme);\n}\n<\/code><\/pre>\n\n\n\n<h4>7. Update the _Layout.cshtml view<\/h4>\n\n\n\n<p>Since my sample adds a menu item named &#8216;Claims&#8217; that will list the claims B2C issued, you need to update the view Views\\Shared\\_Layout.cshtml and add a link to it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"markup\" class=\"language-markup line-numbers\">&lt;li class=\"nav-item\">\n    &lt;a class=\"nav-link text-dark\" asp-area=\"\" asp-controller=\"Home\" asp-action=\"Privacy\">Privacy&lt;\/a>\n&lt;\/li>\n@if (User.Identity.IsAuthenticated)\n{\n    &lt;li class=\"nav-item\">\n        &lt;a class=\"nav-link text-dark\" asp-area=\"\" asp-controller=\"Account\" asp-action=\"Claims\">Claims&lt;\/a>\n    &lt;\/li>\n}\n<\/code><\/pre>\n\n\n\n<h4>8. Add the Claims view<\/h4>\n\n\n\n<p>You need to add the file Views\\Account\\Claims.cshtml and copy the code from the github repo. It&#8217;s just a little snippet that enumerates all claims into html.<\/p>\n\n\n\n<h4>9. Build and capture Service Providers SAML manifest<\/h4>\n\n\n\n<p>With all these changes, you should be able to build and run your application. The first thing you do when it launches is to capture its SAML manifest, since the way a SAML Service Provider (your webapp) and a SAML Identity Provider (B2C) works together is that they access each others SAML manifest to see information on the other party. You can get your webapp&#8217;s manifest by browsing to https:\/\/localhost:5001\/Saml2. That will give you an XML file.<\/p>\n\n\n\n<p>Since you are running your webapp as localhost, B2C will not be able to access your metadata and the authentication will fail. However, you can upload the XML file to anywhere public, like Azure Storage to OneDrive and make it public. Wherever you upload it, make sure that you can reach it via the browser anonymously. <\/p>\n\n\n\n<h4>10. App Registration in Azure AD B2C<\/h4>\n\n\n\n<p>We need to register an application in Azure AD B2C for the webapp. That is a very simple process and the steps are:<\/p>\n\n\n\n<ol><li>+New Application<ol><li>Name = Saml2WebApp or similar<\/li><li>Accounts in this organizational directory only<\/li><li>Redirect URI &#8211; Web + https:\/\/localhost:5001  (the redirect uri is never used for SAML)<\/li><li>Save<br><\/li><\/ol><\/li><li>Open the Manifest<ol><li>change the identifierUris: [] to be as below. This must match EntityId in appsettingsjson<br><br>&#8220;identifiersUris&#8221;: [ &#8220;https:\/\/localhost:5001\/Saml2&#8221; ],<br><\/li><li>change the samlMetadataUrl to be as below (depending on where you stored it)<br><br>&#8220;samlMetadataUrl&#8221;:&nbsp;&#8220;https:\/\/yourstorage.blob.core.windows.net\/public\/localhost.5001_Saml2.xml&#8221;,<\/li><\/ol><\/li><\/ol>\n\n\n\n<h2>Running the Webapp<\/h2>\n\n\n\n<p>With everything ready, you should now be able to run the webapp and sign in and sign out. If you&#8217;re using Chrome or Firefox, I recommend installing the extension SAML-tracer as you get a handy tool that can capture and display the SAML specific messages being exchanged by your app and B2C<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"995\" height=\"653\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-signin-policy.png\" alt=\"\" class=\"wp-image-7728\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-signin-policy.png 995w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-signin-policy-300x197.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-signin-policy-768x504.png 768w\" sizes=\"(max-width: 995px) 100vw, 995px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"995\" height=\"653\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-claims.png\" alt=\"\" class=\"wp-image-7729\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-claims.png 995w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-claims-300x197.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-webapp-claims-768x504.png 768w\" sizes=\"(max-width: 995px) 100vw, 995px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"786\" height=\"593\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-saml-tracer.png\" alt=\"\" class=\"wp-image-7730\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-saml-tracer.png 786w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-saml-tracer-300x226.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/04\/saml-aspnet-saml-tracer-768x579.png 768w\" sizes=\"(max-width: 786px) 100vw, 786px\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Most new application today are built using OIDC\/OAuth protocols, and if you need a ASP.Net sample for that, you have plenty to choose from when working with Azure AD B2C as your identity provider. But if you have to work with the SAML protocol and B2C, the number of samples grow small &#8211; very small. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[444],"tags":[448,446,447],"_links":{"self":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7720"}],"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=7720"}],"version-history":[{"count":5,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7720\/revisions"}],"predecessor-version":[{"id":7775,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7720\/revisions\/7775"}],"wp:attachment":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7720"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7720"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7720"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}