{"id":7702,"date":"2021-02-04T21:12:09","date_gmt":"2021-02-04T20:12:09","guid":{"rendered":"https:\/\/blog.redbaronofazure.com\/?p=7702"},"modified":"2021-05-27T12:49:52","modified_gmt":"2021-05-27T10:49:52","slug":"deploying-azure-ad-b2c-custom-policies-with-azure-devops","status":"publish","type":"post","link":"https:\/\/blog.redbaronofazure.com\/?p=7702","title":{"rendered":"Deploying Azure AD B2C Custom Policies with Azure DevOps"},"content":{"rendered":"\n<p>When working with Azure AD B2C Custom Policies, you are editing xml text files and uploading them to your B2C tenant. The actual uploading can be done with <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/graph\/api\/trustframework-put-trustframeworkpolicy?view=graph-rest-beta\" target=\"_blank\">Microsoft Graph API<\/a>, but since the B2C policy files contain data that is tenant specific, you need to manipulate them before uploading them to a specific environment. This blog will show you one way of doing it. The source code if this sample is available in this github repo <a href=\"https:\/\/github.com\/cljung\/b2c-pipeline-demo\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/cljung\/b2c-pipeline-demo<\/a>.<\/p>\n\n\n\n<h3>Making the B2C policies generic<\/h3>\n\n\n\n<p>When checking in your B2C xml text files to a source repository, you need to make them generic, ie clean them from B2C tenant specific values. Here, I&#8217;ve choosen a style with <em>{config: &#8230;}<\/em> style markup, but how you do it is up to you. It only needs to be unique for search &amp; replace. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"xml\" class=\"language-xml line-numbers\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?>\n&lt;TrustFrameworkPolicy ... \nTenantId=\"{config:yourtenant.onmicrosoft.com}\" \nPolicyId=\"B2C_1A_{config:PolicyPrefix}TrustFrameworkExtensions\" \nPublicPolicyUri=\"http:\/\/{config:yourtenant.onmicrosoft.com}\/B2C_1A_{config:PolicyPrefix}TrustFrameworkExtensions\">\n  &lt;BasePolicy>\n    &lt;TenantId>{config:yourtenant.onmicrosoft.com}&lt;\/TenantId>\n      &lt;PolicyId>B2C_1A_{config:PolicyPrefix}TrustFrameworkBase&lt;\/PolicyId>\n  &lt;\/BasePolicy><\/code><\/pre>\n\n\n\n<p>The markup keywords I use in this sample are the following:<\/p>\n\n\n\n<ul><li>{config:PolicyPrefix} <\/li><li>{config:yourtenant.onmicrosoft.com}<\/li><li>{config:ProxyIdentityExperienceFrameworkAppId}<\/li><li>{config:IdentityExperienceFrameworkAppId}&#8221;<\/li><li>{config:b2c-extension-app:AppId}&#8221;<\/li><li>{config:b2c-extension-app:objectId}<\/li><\/ul>\n\n\n\n<p>PolicyPrefix is a way of making  the PolcyId&#8217;s unique so that you can have multiple policies in one B2C tenant without overwriting them. For instance, it makes B2C_1A_signup_signin show up like B2C_1A_ABC_signup_signin. The other one to call out are the two b2c-extension-apps, since they are the way to bind the policy to which application extension attributes are bound to (read more in the <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory-b2c\/user-flow-custom-attributes?pivots=b2c-custom-policy\" target=\"_blank\">docs<\/a>). In by sample github repo, you can view my B2C generic policies.<\/p>\n\n\n\n<h3>Adapting generic B2C policies to a tenant<\/h3>\n\n\n\n<p>In the Azure DevOps <em>build <\/em>pipeline, a major task is to update the B2C xml text files and take them from a generic state into targeting an environment. So, in essence, the build pipeline is a search &amp; replace task. To do this, I have created a powershell script named <a href=\"https:\/\/github.com\/cljung\/b2c-pipeline-demo\/blob\/main\/scripts\/prepare-b2c-policies.ps1\" target=\"_blank\" rel=\"noreferrer noopener\">prepare-b2c-policies.ps1<\/a> that gets some B2C tenant specific details from the B2C tenant via Microsoft Graph API and then also lets you add your dictionary with words to replace in the file.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"848\" height=\"459\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-01.png\" alt=\"\" class=\"wp-image-7704\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-01.png 848w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-01-300x162.png 300w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-01-768x416.png 768w\" sizes=\"(max-width: 848px) 100vw, 848px\" \/><\/figure>\n\n\n\n<p>The updated B2C policy files are then copied and uploaded as the published build artifacts. For compiled code, this would be copying the EXEs and DLLs and publishing them as output of the build, but for B2C we are just publishing the updated xml text files &#8211; together with the scripts used to deploy them to B2C (that&#8217;s why you have two copy steps).<\/p>\n\n\n\n<p>The script <a href=\"https:\/\/github.com\/cljung\/b2c-pipeline-demo\/blob\/main\/scripts\/prepare-b2c-policies.ps1\" target=\"_blank\" rel=\"noreferrer noopener\">prepare-b2c-policies.ps1<\/a> uses client credentials to query values needed for the apps IdentityExperienceFramework and ProxyIdentityExperienceFramework and for that reason you need to register an app in the B2C tenant, create a secret and give it permission Application.Read.All. <\/p>\n\n\n\n<p>For the build pipeline, the following variables are defined in Azure DevOps:<\/p>\n\n\n\n<ul><li>B2CTenantName &#8211; ie yourtenant.onmicrosoft.com<\/li><li>B2CPolicyPrefix &#8211; ie the &#8220;ABC&#8221; in B2C_1A_ABC_&#8230;<\/li><li>IdentityExperienceFrameworkAppName &#8211; name of that app<\/li><li>ProxyIdentityExperienceFrameworkAppName &#8211; name of that app<\/li><li>ClientCredAppID &#8211; the AppID guid to use for client credentials<\/li><li>ClientCredAppKey &#8211; the client secret for the client credentials<\/li><li>AppInsightInstrumentationKey &#8211; AppInsights InstrumentationKey (guid)<\/li><li>FacebookClientId &#8211; facebook client_id (enter dummy 1234567 just to make the sample work)<\/li><\/ul>\n\n\n\n<h3>Deploying the adapted B2C policies to a tenant<\/h3>\n\n\n\n<p>In the Azure DevOps <em>release<\/em> pipeline, the task is twofold; 1) figure out which order to deploy the policy files (base file first, etc) and 2) deploy them. The script file <a href=\"https:\/\/github.com\/cljung\/b2c-pipeline-demo\/blob\/main\/scripts\/deploy-to-b2c.ps1\" target=\"_blank\" rel=\"noreferrer noopener\">deploy-to-b2c.ps1<\/a> does this and part of solving task 1) is done via figuring out the inheritance chain of the policies. Files that don&#8217;t inherit from others (have no BasePolicyId) are considered to be <em>roots<\/em> and uploading starts with those files and then traversing their children, etc. <\/p>\n\n\n\n<p>The actual uploading of a B2C policy is a Microsoft Graph API http PUT call with the XML as the http body. In order to do so, the app used for client credentials needs to have the permission Policy.ReadWrite.TrustFramework<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"583\" height=\"428\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-02.png\" alt=\"\" class=\"wp-image-7706\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-02.png 583w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-02-300x220.png 300w\" sizes=\"(max-width: 583px) 100vw, 583px\" \/><\/figure>\n\n\n\n<p>This means that the release pipeline is also just a powershell step, this time uploading the xml text files to the B2C tenant.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"765\" height=\"344\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-03.png\" alt=\"\" class=\"wp-image-7707\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-03.png 765w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-03-300x135.png 300w\" sizes=\"(max-width: 765px) 100vw, 765px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"684\" height=\"503\" src=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-04.png\" alt=\"\" class=\"wp-image-7708\" srcset=\"https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-04.png 684w, https:\/\/blog.redbaronofazure.com\/wp-content\/uploads\/2021\/02\/b2c-pipeline-demo-04-300x221.png 300w\" sizes=\"(max-width: 684px) 100vw, 684px\" \/><\/figure>\n\n\n\n<h3>Summary<\/h3>\n\n\n\n<p>This blog post described the concepts of storing generic B2C xml policy files in the source repo and using build and release pipelines to adapt them to the target environment and then deploy them. However, this is only intended to show you the basics and give you a working sample that you can play with. There is more work to do with Azure DevOps, like having more release pipelines and deploy to test, Q&amp;A and production, etc, but that has less to do with B2C than normal Azure DevOps configuration. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>When working with Azure AD B2C Custom Policies, you are editing xml text files and uploading them to your B2C tenant. The actual uploading can be done with Microsoft Graph API, but since the B2C policy files contain data that is tenant specific, you need to manipulate them before uploading them to a specific environment. [&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":[443,445],"_links":{"self":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7702"}],"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=7702"}],"version-history":[{"count":9,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7702\/revisions"}],"predecessor-version":[{"id":7777,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=\/wp\/v2\/posts\/7702\/revisions\/7777"}],"wp:attachment":[{"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=7702"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=7702"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.redbaronofazure.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=7702"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}