<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Richard Roché</title>
    <description>The latest articles on DEV Community by Richard Roché (@rickroche).</description>
    <link>https://dev.to/rickroche</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F177976%2Feb90f0e3-3481-45c5-964b-1588506e8e3f.jpeg</url>
      <title>DEV Community: Richard Roché</title>
      <link>https://dev.to/rickroche</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rickroche"/>
    <language>en</language>
    <item>
      <title>Single Sign-On, Azure Static Web Apps and Azure Active Directory</title>
      <dc:creator>Richard Roché</dc:creator>
      <pubDate>Tue, 01 Mar 2022 10:00:00 +0000</pubDate>
      <link>https://dev.to/rickroche/single-sign-on-azure-static-web-apps-and-azure-active-directory-dh9</link>
      <guid>https://dev.to/rickroche/single-sign-on-azure-static-web-apps-and-azure-active-directory-dh9</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_6l_4Ya_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/azure-static-web-apps-sso-cover.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_6l_4Ya_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/azure-static-web-apps-sso-cover.png" alt="" width="880" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We often build and deploy web applications specifically for users internal to our organisation. &lt;a href="https://azure.microsoft.com/en-us/services/app-service/static/"&gt;Azure Static Web Apps&lt;/a&gt; is proving to be an excellent replacement for &lt;a href="https://azure.microsoft.com/en-us/services/app-service/"&gt;Azure App Service&lt;/a&gt; in these scenarios.&lt;/p&gt;

&lt;p&gt;At a high-level the service provides you with a great set of features (outlined in the &lt;a href="https://azure.microsoft.com/en-us/updates/azure-static-web-apps-is-now-generally-available/"&gt;Azure release notes&lt;/a&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Globally distributed content for production apps&lt;/li&gt;
&lt;li&gt;Tailored CI/CD workflows from code to cloud&lt;/li&gt;
&lt;li&gt;Auto-provisioned preview environments&lt;/li&gt;
&lt;li&gt;Custom domain configuration and free SSL certificates&lt;/li&gt;
&lt;li&gt;Built-in access to a variety of authentication providers&lt;/li&gt;
&lt;li&gt;Route-based authorization&lt;/li&gt;
&lt;li&gt;Custom routing&lt;/li&gt;
&lt;li&gt;Integration with serverless APIs powered by Azure Functions&lt;/li&gt;
&lt;li&gt;A custom Visual Studio Code developer extension&lt;/li&gt;
&lt;li&gt;A feature-rich CLI for local development&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The desired experience?
&lt;/h2&gt;

&lt;p&gt;The experience I wanted to achieve was that if one of our internal users browsed to any of our internal apps, they would be able to use SSO across them provided they were a member of the AAD group needed to access the app (or just a member of our tenant for organisation-wide apps) - no login button, just a seamless logged-in user experience.&lt;/p&gt;

&lt;p&gt;It turns out this was super easy to get right! Follow below!&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication options
&lt;/h2&gt;

&lt;p&gt;Azure Static Web Apps makes authentication easy to enable across the three &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-authorization?tabs=invitations"&gt;pre-configured identity providers&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Active Directory (AAD)&lt;/li&gt;
&lt;li&gt;Github or&lt;/li&gt;
&lt;li&gt;Twitter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These options allow users to login using a login button linking to the desired provider.&lt;/p&gt;

&lt;p&gt;Initially I tried to use the pre-configured AAD provider, and when trying to log in using my company account I was presented with this approval dialogue&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UdyVjPmz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-swa-admin-approval.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UdyVjPmz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-swa-admin-approval.png" alt="Azure Static Web Apps Admin Approval" title="Azure Static Web Apps Admin Approval" width="672" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meaning if I was able to get an admin to grant the permission, all users from our tenant would be able to log in to all Azure Static Web Apps, regardless of who had deployed them making this a non-starter for me.&lt;/p&gt;

&lt;p&gt;Fortunately we already deploy our static web apps using the Standard plan for &lt;a href="https://azure.microsoft.com/en-us/pricing/details/app-service/static/"&gt;$9/per app/month&lt;/a&gt;, giving us the ability to use &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad"&gt;custom authentication&lt;/a&gt; and use SSO with our organisations AAD tenant, internal app registrations and restrict access to groups / users in our tenants directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting it done!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LXW0NZa_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/c4-container.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LXW0NZa_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/c4-container.png" alt="C4 Container Diagram" title="C4 Container Diagram" width="397" height="790"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve the desired experience there are a number of components required&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the static web app (the website)&lt;/li&gt;
&lt;li&gt;an Azure App Registration for your app in your tenant&lt;/li&gt;
&lt;li&gt;an Azure resource group for your project (I'm working on the assumption you already have a subscription, if not &lt;a href="https://docs.microsoft.com/en-us/azure/cost-management-billing/manage/create-subscription"&gt;read here&lt;/a&gt;)

&lt;ul&gt;
&lt;li&gt;an Azure Static Web App for the web app&lt;/li&gt;
&lt;li&gt;an Azure Key Vault to safely store the secrets for your app&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will be showing you how to create, configure and deploy these using &lt;a href="https://docs.github.com/en/actions"&gt;GitHub Actions&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep"&gt;Bicep&lt;/a&gt; (for creating the Azure resources) and some once off scripts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: All code is available on GitHub &lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso"&gt;over here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: It is a good idea to wire up your Key Vault to a Log Analytics workspace or similar to track audit events when in production.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial setup and deployment
&lt;/h2&gt;

&lt;p&gt;First we are going to scaffold our web app, define our infrastructure and deploy to Azure without any auth in place. Once done, we will move onto the configuration of the app with auth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffold the web app
&lt;/h3&gt;

&lt;p&gt;For the purposes of this post, I needed a simple static web app and used the &lt;a href="https://kit.svelte.dev/"&gt;SvelteKit&lt;/a&gt; skeleton project with the &lt;a href="https://github.com/sveltejs/kit/tree/master/packages/adapter-static"&gt;static adapter&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init svelte@next webapp
&lt;span class="nb"&gt;cd &lt;/span&gt;webapp
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @sveltejs/adapter-static@next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to update the default &lt;code&gt;svelte.config.js&lt;/code&gt; to &lt;a href="https://github.com/sveltejs/kit/tree/master/packages/adapter-static#usage"&gt;use the static adapter&lt;/a&gt;, see an example &lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/webapp/svelte.config.js"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the Azure Infrastructure
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Resource Group
&lt;/h4&gt;

&lt;p&gt;First, create an Azure resource group. For this tutorial I'm creating it manually from my terminal and &lt;a href="https://docs.microsoft.com/en-us/cli/azure/"&gt;Azure CLI&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group create &lt;span class="nt"&gt;-g&lt;/span&gt; rg-swa-sso &lt;span class="nt"&gt;-l&lt;/span&gt; northeurope
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Resources
&lt;/h4&gt;

&lt;p&gt;We will be deploying a Static Web App as well as a Key Vault using Bicep. I've split out the Bicep files to make them easier to work with as follows (filenames link to the code on GitHub)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/main.bicep"&gt;&lt;code&gt;main.bicep&lt;/code&gt;&lt;/a&gt; - the main template that is deployed which makes use of the other templates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/get-kv-secrets-refs.bicep"&gt;&lt;code&gt;get-kv-secrets-refs.bicep&lt;/code&gt;&lt;/a&gt; - a helper to build up the Key Vault secret references&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/key-vault.bicep"&gt;&lt;code&gt;key-vault.bicep&lt;/code&gt;&lt;/a&gt; - defines the Key Vault resources and the role assignments needed&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/static-sites.bicep"&gt;&lt;code&gt;static-sites.bicep&lt;/code&gt;&lt;/a&gt; - defines the Static Web App resources needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/infra/main.bicep"&gt;&lt;code&gt;main.bicep&lt;/code&gt;&lt;/a&gt; there are the following highlights to make note of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuring the static web app

&lt;ul&gt;
&lt;li&gt;to have app settings that are Key Vault references (&lt;code&gt;AAD_CLIENT_ID&lt;/code&gt;, &lt;code&gt;AAD_CLIENT_SECRET&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;to use the &lt;code&gt;Standard&lt;/code&gt; sku
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module swa 'static-sites.bicep' = {
  name: 'deploy-swa-${appName}'
  params: {
    appSettings: {
      AAD_CLIENT_ID: refs.outputs.aadClientIdRef
      AAD_CLIENT_SECRET: refs.outputs.aadClientSecretRef
    }
    ...
    sku: {
      name: 'Standard'
      tier: 'Standard'
    }
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;And configuring the key vault to allow the static web app permissions to read from it
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// https://docs.microsoft.com/en-us/azure/key-vault/general/rbac-guide?tabs=azure-cli#azure-built-in-roles-for-key-vault-data-plane-operations
var keyVaultSecretsUserRole = '4633458b-17de-408a-b874-0445c86b69e6'

module kv 'key-vault.bicep' = {
  name: 'deploy-kv-${appName}'
  params: {
    ...
    roleAssignments: [
      {
        roleDefinitionId: keyVaultSecretsUserRole
        principalType: 'ServicePrincipal'
        principalId: swa.outputs.siteSystemAssignedIdentityId
      }
    ]
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy using GitHub
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Deployment credentials
&lt;/h4&gt;

&lt;p&gt;For a GitHub Action to be able to deploy to your resource group you need to have some form of deployment credentials. You can read about the options available &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=userlevel"&gt;here&lt;/a&gt;. I'll be using the &lt;a href="https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli"&gt;Service Principal&lt;/a&gt; approach with &lt;a href="https://docs.microsoft.com/en-us/cli/azure/"&gt;Azure CLI&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az ad sp create-for-rbac &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"sp-swa-sso"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--role&lt;/span&gt; Owner &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--scopes&lt;/span&gt; /subscriptions/&lt;span class="o"&gt;{&lt;/span&gt;subscription-id&lt;span class="o"&gt;}&lt;/span&gt;/resourceGroups/&lt;span class="o"&gt;{&lt;/span&gt;resource-group-name&lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;--sdk-auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will get a JSON output from this that you can then save as a GitHub secret with the name &lt;code&gt;AZURE_CREDENTIALS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;{subscription-id}&lt;/code&gt; with the ID of your subscription&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;{resource-group-name}&lt;/code&gt; with the name of your resource group&lt;/li&gt;
&lt;li&gt;I've made the service principal have Owner access to the resource group (so that I can assign roles).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Workflow token
&lt;/h4&gt;

&lt;p&gt;Azure Static Web Apps needs access to your workflow when deploying. For this we'll set up a &lt;code&gt;WORKFLOW_TOKEN&lt;/code&gt; secret using a GitHub personal access token with the workflow scope. Follow &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token"&gt;the instructions here&lt;/a&gt; to create the token.&lt;/p&gt;

&lt;h4&gt;
  
  
  Github Action Workflow
&lt;/h4&gt;

&lt;p&gt;With the above secrets in place, we can now create a workflow (mines in &lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/6c95cffac91b8d04aefda1921683e03fda5f51bf/.github/workflows/deploy.yaml"&gt;&lt;code&gt;.github/workflows/deploy.yaml&lt;/code&gt;&lt;/a&gt;) which&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;specifies some environment variables for names, tags and locations&lt;/li&gt;
&lt;li&gt;checks out the repo&lt;/li&gt;
&lt;li&gt;logs into Azure using &lt;code&gt;${{ secrets.AZURE_CREDENTIALS }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;deploys to the resource group using the &lt;a href="https://github.com/marketplace/actions/azure-cli-action"&gt;&lt;code&gt;azure/CLI@v1&lt;/code&gt; action&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;gets the API from the deployed static web app (so that we can deploy code to it)&lt;/li&gt;
&lt;li&gt;deploys the webapp using the &lt;a href="https://github.com/Azure/static-web-apps-deploy"&gt;&lt;code&gt;Azure/static-web-apps-deploy@v1&lt;/code&gt; action&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the full workflow below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Infra and App&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;RESOURCE_GROUP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rg-swa-sso'&lt;/span&gt;
  &lt;span class="na"&gt;RESOURCE_TAGS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"owner":"rick.roche",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"app":"azure-swa-sso",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"repo":"https://github.com/rick-roche/azure-static-web-apps-sso"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}'&lt;/span&gt;
  &lt;span class="na"&gt;APP_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;swa-sso'&lt;/span&gt;
  &lt;span class="na"&gt;LOCATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;westeurope'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-infra&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure Login&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;creds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CREDENTIALS }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Infra&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_infra&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name != 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/CLI@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;inlineScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;az deployment group create \&lt;/span&gt;
              &lt;span class="s"&gt;--resource-group ${{ env.RESOURCE_GROUP }} \&lt;/span&gt;
              &lt;span class="s"&gt;--template-file ./infra/main.bicep \&lt;/span&gt;
              &lt;span class="s"&gt;--parameters \&lt;/span&gt;
                  &lt;span class="s"&gt;appName='${{ env.APP_NAME }}' \&lt;/span&gt;
                  &lt;span class="s"&gt;location='${{ env.LOCATION }}' \&lt;/span&gt;
                  &lt;span class="s"&gt;repositoryUrl='https://github.com/rick-roche/azure-static-web-apps-sso' \&lt;/span&gt;
                  &lt;span class="s"&gt;repositoryToken='${{ secrets.WORKFLOW_TOKEN }}' \&lt;/span&gt;
                  &lt;span class="s"&gt;tags='${{ env.RESOURCE_TAGS }}'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get Static Web App API Key&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static_web_app_apikey&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name != 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/CLI@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;inlineScript&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;APIKEY=$(az staticwebapp secrets list --name 'stapp-${{ env.APP_NAME }}' | jq -r '.properties.apiKey')&lt;/span&gt;
            &lt;span class="s"&gt;echo "::set-output name=APIKEY::$APIKEY"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy WebApp to Static Web App&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static_web_app_deploy&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name != 'pull_request'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure/static-web-apps-deploy@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;azure_static_web_apps_api_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.static_web_app_apikey.outputs.APIKEY }}&lt;/span&gt;
          &lt;span class="na"&gt;repo_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt; &lt;span class="c1"&gt;# Used for GitHub integrations (i.e. PR comments)&lt;/span&gt;
          &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;upload'&lt;/span&gt;
          &lt;span class="c1"&gt;# Build configuration for Azure Static Web Apps: https://aka.ms/swaworkflowconfig&lt;/span&gt;
          &lt;span class="na"&gt;app_location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;webapp'&lt;/span&gt;
          &lt;span class="na"&gt;api_location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
          &lt;span class="na"&gt;output_location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;build'&lt;/span&gt; &lt;span class="c1"&gt;# relative to app_location&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finalising auth
&lt;/h2&gt;

&lt;p&gt;At this stage you should have a GitHub workflow successfully deploying a Static Web App and a Key Vault to you resource group. You should also be able to browse to your web app using the generated URL (navigate into the Static Web App from the portal, and you will see your URL on the overview tab. e.g. &lt;a href="https://gray-wave-03fb32a03.1.azurestaticapps.net"&gt;https://gray-wave-03fb32a03.1.azurestaticapps.net&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UKPcQtEq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-azure-swa-overview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UKPcQtEq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-azure-swa-overview.png" alt="Static Web App Overview" title="Static Web App Overview" width="880" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Your app won't ask you to authenticate just yet!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To enable auth our next steps will be to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create an AAD app registration&lt;/li&gt;
&lt;li&gt;add it's client ID and secret as secrets in your key vault&lt;/li&gt;
&lt;li&gt;configure the static web app to auto log you in if you aren't already or your token has expired&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AAD App Registration
&lt;/h3&gt;

&lt;p&gt;An AAD app registration allows us to bind our application to a desired set of authentication flows and restrictions. Think of it as giving your application an identity inside AAD. &lt;a href="https://www.re-mark-able.net/understanding-azure-active-directory-application-registrations/"&gt;Mark Foppen wrote a lovely article&lt;/a&gt; that may help demystify this a bit.&lt;/p&gt;

&lt;p&gt;In this tutorial I'll just be using &lt;a href="https://docs.microsoft.com/en-us/cli/azure/"&gt;Azure CLI&lt;/a&gt; to create one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az ad app create &lt;span class="nt"&gt;--display-name&lt;/span&gt; aadapp-swa-sso &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--available-to-other-tenants&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--identifier-uris&lt;/span&gt; api://stapp-swa-sso &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--reply-urls&lt;/span&gt; &lt;span class="s1"&gt;'https://gray-wave-03fb32a03.1.azurestaticapps.net/.auth/login/aad/callback'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--native-app&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: &lt;br&gt;
Ensure you set the &lt;code&gt;--reply-urls&lt;/code&gt; to be the generated URL of your app with the &lt;code&gt;/.auth/login/aad/callback&lt;/code&gt; suffix. This allows AAD to call your app once the auth flow has completed.&lt;/p&gt;

&lt;p&gt;Once created, we need to create an application secret for the application. For this tutorial you can do this via the portal following the &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret"&gt;instructions here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Copy the value of the secret as well as the &lt;code&gt;Application (client) ID&lt;/code&gt; of the AAD app (found under the Overview section of the app).&lt;/p&gt;
&lt;h3&gt;
  
  
  Setup Key Vault Secrets
&lt;/h3&gt;

&lt;p&gt;Once again, I've used the portal for this tutorial. You can follow the &lt;a href="https://docs.microsoft.com/en-us/azure/key-vault/secrets/quick-create-portal#add-a-secret-to-key-vault"&gt;guide here&lt;/a&gt; setting two secrets in your vault&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aadClientId&lt;/code&gt; - this should be set to the Application (client) ID of your app registration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aadClientSecret&lt;/code&gt; - this should be set to the value of the application secret created above&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Configuring the Static Web App
&lt;/h3&gt;

&lt;p&gt;Configuration for Azure Static Web Apps is defined in the &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/configuration"&gt;&lt;code&gt;staticwebapp.config.json&lt;/code&gt;&lt;/a&gt; file, which controls, among other things, authentication and authorisation.&lt;/p&gt;

&lt;p&gt;I've put mine in the &lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso/blob/main/webapp/staticwebapp.config.json"&gt;root of the webapp folder&lt;/a&gt; and set it to do the following to enable the auto-login SSO magic&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable custom &lt;code&gt;auth&lt;/code&gt; with &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad#azure-active-directory-version-2"&gt;Azure Active Directory Version 2&lt;/a&gt; using the app settings references from earlier (&lt;code&gt;AAD_CLIENT_ID&lt;/code&gt;, &lt;code&gt;AAD_CLIENT_SECRET&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"identityProviders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"azureActiveDirectory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"registration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"openIdIssuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://login.microsoftonline.com/4af290c8-08df-440d-93db-cbac02bd9b19/v2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"clientIdSettingName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAD_CLIENT_ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"clientSecretSettingName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAD_CLIENT_SECRET"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;a &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/configuration#fallback-routes"&gt;&lt;code&gt;navigationFallback&lt;/code&gt; route&lt;/a&gt; to &lt;code&gt;index.html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"navigationFallback"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rewrite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;specific rules for the apps &lt;code&gt;routes&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creates a &lt;code&gt;/login&lt;/code&gt; route redirecting to AAD allowing anonymous access
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rewrite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.auth/login/aad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowedRoles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"anonymous"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authenticated"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/authentication-authorization?tabs=invitations#block-an-authentication-provider"&gt;blocks all providers except AAD&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.auth/login/github"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.auth/login/twitter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;creates a &lt;code&gt;/logout&lt;/code&gt; route redirecting to AAD allowing anonymous access
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/.auth/logout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowedRoles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"anonymous"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authenticated"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;ul&gt;
&lt;li&gt;enforces auth for all other routes (&lt;code&gt;/*&lt;/code&gt;)
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"allowedRoles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"authenticated"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sets up a response override that if an unauthenticated user hits a page, they should be redirected to the &lt;code&gt;/login&lt;/code&gt; route&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"responseOverrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"401"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The combination of all of the above means that anyone accessing the site that isn't logged in will be routed to the &lt;code&gt;/login&lt;/code&gt; route which will ensure that the AAD auth flow is completed!&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing up!
&lt;/h2&gt;

&lt;p&gt;Commit all your outstanding changes, push to GitHub and watch your action deploy... once done, access your web app in the browser, and it should redirect you to login&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B1noNu-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-login.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B1noNu-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-login.png" alt="Azure AD Login" title="Azure AD Login" width="879" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initially you will need to provide admin consent for your AD app registration to read the logged-in user details&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1tJtkL8q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-consent.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1tJtkL8q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-aad-consent.png" alt="Azure AD User Consent" title="Azure AD User Consent" width="880" height="1108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If all has gone well you should see the default page after login completes&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eWYDTKdS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-welcome.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eWYDTKdS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.rickroche.com/2022/03/single-sign-on-azure-static-web-apps-and-azure-active-directory/screenshot-welcome.png" alt="Welcome Page" title="Welcome Page" width="880" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it! Hope you enjoyed this tutorial and that it helps you setup SSO for your Static Web Apps!&lt;/p&gt;

&lt;p&gt;Note: &lt;br&gt;
A reminder that all code is available on GitHub &lt;a href="https://github.com/rick-roche/azure-static-web-apps-sso"&gt;over here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Featured image background by &lt;a href="https://unsplash.com/@helloimnik?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Hello I'm Nik&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/sign-in?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>staticwebapps</category>
      <category>sso</category>
      <category>aad</category>
    </item>
    <item>
      <title>Bicep Modules: Refactor, Compose, Reuse</title>
      <dc:creator>Richard Roché</dc:creator>
      <pubDate>Sun, 11 Jul 2021 11:00:00 +0000</pubDate>
      <link>https://dev.to/rickroche/bicep-modules-refactor-compose-reuse-51p1</link>
      <guid>https://dev.to/rickroche/bicep-modules-refactor-compose-reuse-51p1</guid>
      <description>&lt;p&gt;In my previous post I touched on the things I learnt while &lt;a href="https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/"&gt;migrating ARM templates to Bicep&lt;/a&gt;. &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview"&gt;Bicep&lt;/a&gt; also introduces the concept of modules to enable template reuse. I took some time to refactor a composite application that had already been converted from using ARM to Bicep templates, to use Bicep modules. This post will cover the things that I learnt by working through that process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why modules?
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/modules"&gt;Bicep documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bicep enables you to break down a complex solution into modules. A Bicep module is a set of one or more resources to be deployed together. Modules abstract away complex details of the raw resource declaration, which can increase readability. You can reuse these modules, and share them with other people. Bicep modules are transpiled into a single ARM template with &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/linked-templates#nested-template"&gt;nested templates&lt;/a&gt; for deployment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the traps we fell into with ARM templates was duplicating templates to make composing and deploying the resources we need easier. Any opportunity to make the deployment of infrastructure more readable, more reusable, and more composable are excellent reasons for me to give it a go. My goal when doing this refactor was to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get rid of any duplication by creating fine-grained modules, designed to be reused&lt;/li&gt;
&lt;li&gt;ensure that all main templates are super easy to use by composing modules together in a way that makes sense for the application&lt;/li&gt;
&lt;li&gt;enable reusability of the fine-grained modules in other projects going forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: let's make the infra readable, composable and reusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notable learnings
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reusable Modules
&lt;/h3&gt;

&lt;p&gt;I went with the approach of trying to make a reusable module for each Azure resource type and putting the modules into a folder called &lt;code&gt;modules&lt;/code&gt; and making sub folders for template groupings. For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;modules/
    appInsights.bicep
    appServicePlan.bicep
    functionApp.bicep
    logAnalytics.bicep
    storageAccount/
        storageAccount.bicep
        tables.bicep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Personally I prefer to have one module to create a storage account and another to add tables to that storage account etc. This level of granularity felt like a good place to start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referencing existing resources inside a module
&lt;/h3&gt;

&lt;p&gt;By making fine-grained modules, there were a number of use cases where I would need to reference an existing resource. For example, creating tables in a storage account requires an existing storage account. &lt;a href="https://github.com/Azure/bicep/blob/main/docs/spec/resources.md#referencing-existing-resources"&gt;Referencing an existing resource&lt;/a&gt; is really easy -- you only need to know its name and can reference it as follows,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Lookup an existing resource
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-02-01' existing = {
  name: storageAccountName
  // Optional if the existing resource is in a different resource group or subscription
  scope: resourceGroup(subscriptionId, resourceGroupName)
}

// You can now use the resource as if you had created it. e.g.
outputs storageAccountResourceId = storageAccount.id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loops!
&lt;/h3&gt;

&lt;p&gt;I really like the &lt;a href="https://github.com/Azure/bicep/blob/main/docs/spec/loops.md"&gt;loops&lt;/a&gt; feature. This allows you to iterate over an array setting multiple properties or creating multiple resources etc. This came in super handy for a storage account tables module that can create multiple tables in one go. E.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param storageAccountName string
param tables array = [
  {
    container: 'default'
    name: 'replace'
  }
]

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-02-01' existing = {
  name: storageAccountName
}

resource storageAccountTables 'Microsoft.Storage/storageAccounts/tableServices/tables@2021-02-01' = [for table in tables: {
  name: '${storageAccount.name}/${table.container}/${table.name}'
  dependsOn: [
    storageAccount
  ]
}]

output storageAccountTableNames array = [for (table, i) in tables: {
  name: storageAccountTables[i].name
}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the use of the different styles of the loops in the &lt;code&gt;storageAccountTables&lt;/code&gt; resource and the outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functions and Expressions
&lt;/h3&gt;

&lt;p&gt;There are loads of &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions"&gt;functions&lt;/a&gt; and &lt;a href="https://github.com/Azure/bicep/blob/main/docs/spec/expressions.md"&gt;expressions&lt;/a&gt; that you can use in your Bicep files and I won't go into all of them. There were a few that I used regularly, and it's hopefully useful that I call them out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Union
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;union(arg1, arg2, arg3, ...)&lt;/code&gt;&lt;br&gt;
Returns a single array or object with all elements from the parameters. Duplicate values or keys are only included once.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I used union everywhere I wanted to have fixed (opinionated) defaults in the module, but allow additional parameters to be merged in. For example, with function apps I defined base settings I want all function apps to have and still allow for additional app settings to be passed in and merged with the base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@description('Additional app settings for your function app')
param additionalAppSettings object = {}

var appSettingsBase = {
  FUNCTIONS_EXTENSION_VERSION: '~3'
  FUNCTIONS_WORKER_RUNTIME: 'dotnet'
  WEBSITE_RUN_FROM_PACKAGE: '1'
  AzureWebJobsStorage: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, storageAccount.apiVersion).keys[0].value}'
}

var appSettings = union(appSettingsBase, additionalAppSettings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also get rid of all the hardcoded schema API versions when looking up keys against a resource by using &lt;code&gt;&amp;lt;resource&amp;gt;.apiVersion&lt;/code&gt; as well as hardcoded suffixes by using the &lt;a href="https://www.rickroche.com/2021/06/migrating-azure-arm-templates-to-bicep/#environment-urls"&gt;environment function&lt;/a&gt;, in this case using the storage suffix: &lt;code&gt;environment().suffixes.storage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another super useful call out would be to the &lt;code&gt;listKeys&lt;/code&gt; function. It allows you to get connection strings or keys from your resources and is super handy (see the &lt;code&gt;AzureWebJobsStorage&lt;/code&gt; example above). &lt;a href="https://blog.johnnyreilly.com/about"&gt;John Reilly&lt;/a&gt; went into &lt;a href="https://blog.johnnyreilly.com/2021/07/07/output-connection-strings-and-keys-from-azure-bicep"&gt;detail on this over here&lt;/a&gt;, please have a read!&lt;/p&gt;

&lt;h4&gt;
  
  
  Ternary
&lt;/h4&gt;

&lt;p&gt;I found that with Bicep I use the &lt;a href="https://github.com/Azure/bicep/blob/main/docs/spec/expressions.md#ternary-operator"&gt;ternary operator&lt;/a&gt; a lot in my main templates when composing the modules. For example, only enabling a secondary region when the environment is &lt;code&gt;nonprod&lt;/code&gt; or &lt;code&gt;prod&lt;/code&gt; but not in &lt;code&gt;dev&lt;/code&gt; can easily be described as,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var secondaryRegionEnabled = (contains(env, 'prod')) ? true : false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much cleaner and readable without all the JSON around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing modules together
&lt;/h2&gt;

&lt;p&gt;Every Bicep file can be consumed as a module which is an awesome feature. I chose to break my files up as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;infra/
  myApp/
    main.bicep - loads up the app.bicep and monitoring.bicep modules
    app.bicep - uses the fine grained modules - app service plans, functions, azure storage etc
    monitoring.bicep - uses the fine grained modules - app insights, log analytics etc
shared-infra/
  modules/
    &amp;lt;all the fine-grained modules&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my &lt;code&gt;main.bicep&lt;/code&gt; I reference the two local Bicep files as modules&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module monitoring 'monitoring.bicep' = {
  name: '${appName}-monitoring'
  params: {
    tags: tags
    location: location
    appInsightsName: appInsightsName
    logAnalyticsWsName: logAnalyticsWsName
  }
}

module app 'app.bicep' = {
  name: '${appName}-app'
  params: {
    tags: tags
    location: location
    appName: appName
    storageAccountName: storageAccountName
    appInsightsName: monitoring.outputs.appInsightsName
    logAnalyticsWsName: monitoring.outputs.logAnalyticsWsName
    monitoringResourceGroup: monitoring.outputs.ResourceGroupName
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;app&lt;/code&gt; module relies on the outputs of the monitoring module -- this helps Bicep figure out the dependencies so that you don't need to define &lt;code&gt;dependsOn&lt;/code&gt; any more.&lt;/p&gt;

&lt;p&gt;Then in &lt;code&gt;monitoring.bicep&lt;/code&gt; I can reference fine-grained reusable modules that I'd like to share with other projects in much the same way&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module log '../../shared-infra/modules/logAnalytics.bicep' = {
  name: '${appName}-log'
  params: {
    tags: tags
    location: location
    logAnalyticsWsName: logAnalyticsWsName
  }
}

module appi '../../shared-infra/modules/appInsights.bicep' = {
  name: '${appName}-appi'
  params: {
    tags: tags
    location: location
    appInsightsName: appInsightsName
    logAnalyticsWsName: log.outputs.logAnalyticsWsName
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another thing to note is the &lt;code&gt;name&lt;/code&gt; of your module is what is shown in the &lt;code&gt;Deployments&lt;/code&gt; tab of the Azure Portal, so make these make sense to you for easy debugging if deployments are breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing modules
&lt;/h2&gt;

&lt;p&gt;At this stage I've got different two styles of modules -- app specific modules breaking up my &lt;code&gt;main.bicep&lt;/code&gt;, making it easier to read and maintain and fine-grained templates in a &lt;code&gt;modules&lt;/code&gt; folder that I can use across all the apps in my &lt;code&gt;infra&lt;/code&gt; folder. Readable -- check! Composable -- check! Reusable -- only inside this repo, so half a check!&lt;/p&gt;

&lt;p&gt;The current version of Bicep (&lt;code&gt;v0.4.63&lt;/code&gt;), does not have a native mechanism to externally share modules across projects. The good news is that this is being looked at and will &lt;em&gt;hopefully&lt;/em&gt; be in the v0.5 release. The issues to watch are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/bicep/issues/2128"&gt;[Story] Bicep Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/bicep/issues/660"&gt;Ability to reference "external" modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Azure/bicep/issues/1242"&gt;Modules need versions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the interim, I am using &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules"&gt;Git submodules&lt;/a&gt; to solve this, although this will not work with all CI/CD tooling when using private repos. To enable this, I moved the modules in &lt;code&gt;shared-infra&lt;/code&gt; into its own repo and added it back to my project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git submodule add &amp;lt;path-to-repo&amp;gt; shared-infra/
git submodule update &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing locally
&lt;/h2&gt;

&lt;p&gt;To quickly validate the individual modules and main templates on my local machine, I wrote a &lt;a href="https://gist.github.com/rick-roche/57c90abae06630060afce1e76372b13b"&gt;simple bash script&lt;/a&gt; to either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build the Bicep file, outputting to the terminal rather than writing to file or&lt;/li&gt;
&lt;li&gt;validate the Bicep template against a resource group
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;RG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace-with-your-rg
&lt;span class="nv"&gt;SUB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;replace-with-your-sub

&lt;span class="nv"&gt;op&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="c"&gt;# lint, validate, create&lt;/span&gt;
&lt;span class="nv"&gt;main_shared&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$main_shared&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'main'&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'./infra'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'main.bicep'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$main_shared&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'shared'&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'./shared-infra'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*.bicep'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi

&lt;/span&gt;lint &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    az bicep build &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--stdout&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

validate &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    az deployment group validate &lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--subscription&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;$op&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script allows one to test either &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;shared&lt;/code&gt; templates, linting them or validating them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./infra.sh lint main
./infra.sh lint shared
./infra.sh validate main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finishing up
&lt;/h2&gt;

&lt;p&gt;After the refactor, my project is in a far cleaner state and the infrastructure is much easier to follow. A second plus is that we now have a separate repo of our shared modules that can become a shared asset across our various teams (using Git submodules for now and the Bicep registry in the future).&lt;/p&gt;

&lt;p&gt;Featured image background by &lt;a href="https://unsplash.com/@lazycreekimages?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Michael Dziedzic&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/modules?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bicep</category>
      <category>arm</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Migrating Azure ARM templates to Bicep</title>
      <dc:creator>Richard Roché</dc:creator>
      <pubDate>Fri, 18 Jun 2021 07:00:00 +0000</pubDate>
      <link>https://dev.to/rickroche/migrating-azure-arm-templates-to-bicep-pok</link>
      <guid>https://dev.to/rickroche/migrating-azure-arm-templates-to-bicep-pok</guid>
      <description>&lt;p&gt;You may have heard of &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview"&gt;Bicep&lt;/a&gt;, and you may be wondering how much effort it is going to take to move all your ARM templates to this new way of deploying Azure resources.&lt;/p&gt;

&lt;p&gt;I gave migrating from ARM to Bicep a go. This post will cover going from JSON ARM templates to shiny new Bicep templates that have no errors and don't contain any warnings or linting issues!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should you migrate?
&lt;/h2&gt;

&lt;p&gt;If you have ever deployed infra to Azure you have most likely used ARM templates before. They do the job, the docs aren't bad and there are built in tasks for ADO which make deploying them super straightforward. However, working with JSON is always going to be verbose, ARM templates have a lot of boilerplate required, making reusable templates is clunky and deployments can get tricky.&lt;/p&gt;

&lt;p&gt;Aiming to address these (and other) issues, Azure is working on a project called Bicep. From the &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview"&gt;projects overview&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. We believe Bicep offers the best authoring experience for your Azure infrastructure as code solutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essentially it is a DSL built on-top of ARM that makes the development of templates simpler and promotes reuse through modules. Judging by the release rate &lt;a href="https://github.com/Azure/bicep/releases"&gt;on their GitHub&lt;/a&gt; the team is working hard to make it awesome very quickly and according to them, as of v0.3, Bicep is now supported by Microsoft Support Plans and Bicep has 100% parity with what can be accomplished with ARM Templates. So Bicep is definitely prod ready (at time of writing v0.4.63 was the latest release)!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Ensure you have the &lt;a href="https://github.com/Azure/bicep/releases"&gt;latest version&lt;/a&gt; of Bicep installed (excellent installation guide &lt;a href="https://github.com/Azure/bicep/blob/main/docs/installing.md"&gt;here&lt;/a&gt;). I went with the &lt;code&gt;az cli&lt;/code&gt; install option and all my examples will be using that variant. I also use Visual Studio Code and installed the &lt;a href="https://github.com/Azure/bicep/blob/main/docs/installing.md#install-the-bicep-vs-code-extension"&gt;Bicep VS Code Extension&lt;/a&gt; for enhanced editing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decompiling ARM to Bicep
&lt;/h2&gt;

&lt;p&gt;First things first, it is possible to &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/decompile?tabs=azure-cli"&gt;decompile&lt;/a&gt; an ARM template into Bicep by running the below command (all our ARM templates are in separate folders with an &lt;code&gt;azuredeploy.json&lt;/code&gt; file for the template)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az bicep decompile &lt;span class="nt"&gt;--file&lt;/span&gt; azuredeploy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;azuredeploy.bicep&lt;/code&gt; file in the same directory. Depending on your template, you will most likely get a stream of yellow warnings in the console, or potentially some red errors: don't panic! Even a single error will result in all the console output being red (at least on macOS) and at the very least you will get this warning message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WARNING: Decompilation is a best-effort process, as there is no guaranteed mapping from ARM JSON to Bicep.&lt;br&gt;
You may need to fix warnings and errors in the generated bicep file(s), or decompilation may fail entirely if an accurate conversion is not possible.&lt;br&gt;
If you would like to report any issues or inaccurate conversions, please see &lt;a href="https://github.com/Azure/bicep/issues"&gt;https://github.com/Azure/bicep/issues&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;It is important to note that the migration from ARM to Bicep will highlight issues in your ARM templates. ARM was more forgiving in certain aspects, after the migration, your templates will be in a better state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Errors come in the form of &lt;code&gt;Error BCPXXX: Description&lt;/code&gt; and the descriptions generally let you get to the root of the problem quickly. E.g. &lt;em&gt;Error BCP037: The property "location" is not allowed on objects of type "Microsoft.EventHub/namespaces/eventhubs". Permissible properties include "dependsOn".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the decompilation gives you any errors, my preferred approach is to fix the ARM template and then run the decompilation again until you get a "clean" decompilation (warnings are fine, just ensure you decompile with no errors).&lt;/p&gt;

&lt;p&gt;Warnings come in two flavours, ones that have a code (&lt;em&gt;Warning BCPXXX&lt;/em&gt;) and ones that don't have a code but have a link at the end. As of &lt;code&gt;v0.4&lt;/code&gt; there is a &lt;a href="https://github.com/Azure/bicep/blob/main/docs/linter.md"&gt;linter&lt;/a&gt; which helps you get your templates into great shape -- these are the warnings with the links at the end. Both kinds of warnings are generally quick to fix and are sensible updates to start getting the benefits from Bicep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common errors and how to fix them
&lt;/h2&gt;

&lt;h3&gt;
  
  
  User defined functions are not supported (BCP007, BCP057)
&lt;/h3&gt;

&lt;p&gt;If you have created user defined functions in ARM, these will not be decompiled (follow the &lt;a href="https://github.com/Azure/bicep/issues/2"&gt;issue&lt;/a&gt;) and you will get two cryptic errors&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error BCP007: This declaration type is not recognized. Specify a parameter, variable, resource, or output declaration.&lt;/p&gt;

&lt;p&gt;Error BCP057: The name "&lt;code&gt;functionName&lt;/code&gt;" does not exist in the current context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To fix, move the function logic into your template (this may require duplication, but can be neatened up in the Bicep template later)&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested dependsOn (BCP034)
&lt;/h3&gt;

&lt;p&gt;Sometimes the use of nested &lt;code&gt;dependsOn&lt;/code&gt; properties in your ARM templates gets weird with Bicep (&lt;code&gt;dependsOn&lt;/code&gt; can often be &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax#resource-dependencies"&gt;removed entirely&lt;/a&gt; in Bicep). The ARM version would have validated and deployed perfectly fine however you will get the following error when decompiling to Bicep.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error BCP034: The enclosing array expected an item of type "module[] | (resource | module) | resource[]", but the provided item was of type "string".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fortunately &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax#resource-dependencies"&gt;Bicep handles dependencies&lt;/a&gt; in a much smarter way than ARM meaning you can safely remove your nested dependencies and run again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For Bicep, you can set an explicit dependency but this approach isn't recommended. Instead, rely on implicit dependencies. An implicit dependency is created when one resource declaration references the identifier of another resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To fix, delete your nested dependencies and validate (consider removing your &lt;code&gt;dependsOn&lt;/code&gt; references entirely)&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Strict schema validation (BCP037, BCP073)
&lt;/h3&gt;

&lt;p&gt;Bicep validates the schema of each resource much more diligently than ARM. As a result you will probably find a bunch of places where you have added properties like &lt;code&gt;location&lt;/code&gt; or &lt;code&gt;tags&lt;/code&gt; to a resource that doesn't support them and ARM didn't mind this. These will be expressed as &lt;code&gt;Error BCP037&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;E.g. I had &lt;code&gt;location&lt;/code&gt; on &lt;a href="https://docs.microsoft.com/en-us/azure/templates/microsoft.eventhub/namespaces/eventhubs?tabs=json"&gt;Microsoft.EventHub/namespaces/eventhubs&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error BCP037: The property "location" is not allowed on objects of type "Microsoft.EventHub/namespaces/eventhubs". Permissible properties include "dependsOn".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The one error I did run into was for &lt;a href="https://azure.microsoft.com/en-us/services/logic-apps/"&gt;Azure Logic Apps&lt;/a&gt; where the schema for &lt;a href="https://docs.microsoft.com/en-us/azure/templates/microsoft.logic/workflows?tabs=json"&gt;Microsoft.Logic/workflows&lt;/a&gt; is missing the &lt;code&gt;identity&lt;/code&gt; property needed for using Managed Identity with your Logic App:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error BCP037: The property "identity" is not allowed on objects of type "Microsoft.Logic/workflows". Permissible properties include "dependsOn".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another common error I encountered was having read-only parameters in my templates (these tend to come along if you have exported templates from the Azure Portal). E.g.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error BCP073: The property "kind" is read-only. Expressions cannot be assigned to read-only properties.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To fix both types of errors, remove the properties that the error messages highlight and run the decompilation again.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Reserved words as parameters (BCP079)
&lt;/h3&gt;

&lt;p&gt;In ARM templates you can have the same name for a parameter, variable, function etc as these are all addressed directly using &lt;code&gt;parameters('name')&lt;/code&gt; or &lt;code&gt;variables('name')&lt;/code&gt; etc. In Bicep the syntax is simplified and as such you will get errors if you have used a reserved word as a parameter. We had a couple of templates taking in a parameter called &lt;code&gt;description&lt;/code&gt; resulting in a cryptic error message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Error BCP079: This expression is referencing its own declaration, which is not allowed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To fix, rename the parameter in your ARM template, update its usages and run the decompilation again.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Common warnings and how to fix them
&lt;/h2&gt;

&lt;p&gt;Hopefully by now you are out of the error zone, for warnings you can now start editing the Bicep file itself. The &lt;a href="https://github.com/Azure/bicep/blob/main/docs/installing.md#install-the-bicep-vs-code-extension"&gt;Bicep VS Code Extension&lt;/a&gt; gives you great intellisense and highlights issues in the Bicep file.&lt;/p&gt;

&lt;p&gt;To test an update on your Bicep file, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az bicep build &lt;span class="nt"&gt;--file&lt;/span&gt; azuredeploy.bicep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Schema warnings, enums and types (BCP035, BCP036, BCP037, BCP073, BCP081, BCP174)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/data-types"&gt;Data types&lt;/a&gt; in Bicep are stricter than in ARM. If you have used &lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt; for booleans these need to be updated to be &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt;. When using enums, they need to match the enums in the schema exactly (case-sensitive). E.g. if you used &lt;code&gt;ascending&lt;/code&gt; and the template schema defines &lt;code&gt;Ascending&lt;/code&gt; this adjustment needs to be made. Examples of these warnings are shown below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning BCP036: The property "kafkaEnabled" expected a value of type "bool | null" but the provided value is of type "'False' | 'True'".&lt;/p&gt;

&lt;p&gt;Warning BCP036: The property "order" expected a value of type "'Ascending' | 'Descending' | null" but the provided value is of type "'ascending'".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Similar to the errors thrown by BCP037 and BCP073 you will get warnings on schema mismatches using codes BCP035, BCP037 and BCP073. These highlight schema mismatches, missing properties and read-only properties used. Examples below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning BCP035: The specified "object" declaration is missing the following required properties: "options".&lt;/p&gt;

&lt;p&gt;Warning BCP037: The property "apiVersion" is not allowed on objects of type "Output". No other properties are allowed.&lt;/p&gt;

&lt;p&gt;Warning BCP073: The property "status" is read-only. Expressions cannot be assigned to read-only properties.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I did find an instance where the Bicep template matches the schema perfectly however warnings are still thrown. I think this is due to &lt;a href="https://github.com/Azure/bicep/issues/3215"&gt;this issue&lt;/a&gt; and fortunately doesn't break anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To fix, update the offending property to match the schema or data type&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  String interpolation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;concat&lt;/code&gt; gets to disappear in Bicep thanks to the lovely string interpolation option. I experienced two variants:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning prefer-interpolation: Use string interpolation instead of the concat function. [&lt;a href="https://aka.ms/bicep/linter/prefer-interpolation"&gt;https://aka.ms/bicep/linter/prefer-interpolation&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To fix, search for all usages of &lt;code&gt;concat&lt;/code&gt; and replace with the new syntax: &lt;code&gt;'string-${var}'&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning simplify-interpolation: Remove unnecessary string interpolation. [&lt;a href="https://aka.ms/bicep/linter/simplify-interpolation"&gt;https://aka.ms/bicep/linter/simplify-interpolation&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;To fix, remove the &lt;code&gt;${}&lt;/code&gt; and reference the variable directly. E.g. &lt;code&gt;'${variable}'&lt;/code&gt; becomes &lt;code&gt;variable&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment URLs
&lt;/h3&gt;

&lt;p&gt;We had a lot of hardcoded URL's in our templates for storage suffixes, front door etc. There is a much better way to do this by using the &lt;code&gt;environment()&lt;/code&gt; function. Caveat here, if you had &lt;code&gt;environment&lt;/code&gt; as a parameter to your ARM template, update this to be something else like &lt;code&gt;env&lt;/code&gt; for example otherwise you won't be able to use the function.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning no-hardcoded-env-urls: Environment URLs should not be hardcoded. Use the environment() function to ensure compatibility across clouds. Found this disallowed host: "core.windows.net" [&lt;a href="https://aka.ms/bicep/linter/no-hardcoded-env-urls"&gt;https://aka.ms/bicep/linter/no-hardcoded-env-urls&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;*&lt;em&gt;To fix, have a look at all the &lt;a href="https://docs.microsoft.com/en-za/azure/azure-resource-manager/templates/template-functions-deployment?tabs=json#environment"&gt;URLs that the environment function provides&lt;/a&gt; and replace your hard-coded version with &lt;code&gt;environment().&amp;lt;property&amp;gt;&lt;/code&gt;. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;E.g. for Azure Storage where &lt;code&gt;core.windows.net&lt;/code&gt; had been hard coded, replacing with &lt;code&gt;environment().suffixes.storage&lt;/code&gt; gives the desired result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scopes (BCP174)
&lt;/h3&gt;

&lt;p&gt;Bicep introduces the concept of target scopes which dictates the scope that resources within that deployment are created in. This gives you a new way to define resources where you previously would have used the &lt;code&gt;/providers/&lt;/code&gt; syntax (role assignments, diagnostic settings etc). The warning you get looks as follows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Warning BCP174: Type validation is not available for resource types declared containing a "/providers/" segment. Please instead use the "scope" property. [&lt;a href="https://aka.ms/BicepScopes"&gt;https://aka.ms/BicepScopes&lt;/a&gt;]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are the most time-consuming to fix, essentially you need to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the schema reference of the actual resource (so for diagnostic settings us &lt;code&gt;microsoft.insights/diagnosticSettings@2017-05-01-preview&lt;/code&gt;) instead of the parent resource &lt;code&gt;/providers&lt;/code&gt;/&lt;/li&gt;
&lt;li&gt;add a &lt;code&gt;scope&lt;/code&gt; referencing the parent resource&lt;/li&gt;
&lt;li&gt;rename so as not to reference the parent resource&lt;/li&gt;
&lt;li&gt;remove unnecessary properties and &lt;code&gt;dependsOn&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource functionAppLogAnalytics 'Microsoft.Web/sites/providers/diagnosticSettings@2017-05-01-preview' = {
  name: '${functionAppName}/Microsoft.Insights/LogAnalytics'
  tags: tagsVar
  properties: {
    name: 'LogAnalytics'
    workspaceId: resourceId(logAnalyticsResourceGroup, 'Microsoft.OperationalInsights/workspaces', logAnalyticsWsName)
    logs: [
      {
        category: 'FunctionAppLogs'
        enabled: true
      }
    ]
  }
  dependsOn: [
    functionApp
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource functionAppLogAnalytics 'microsoft.insights/diagnosticSettings@2017-05-01-preview' = {
  name: LogAnalytics
  scope: functionApp
  properties: {
    workspaceId: logAnalyticsResourceId
    logs: [
      {
        category: 'FunctionAppLogs'
        enabled: true
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Should you migrate?
&lt;/h2&gt;

&lt;p&gt;A resounding yes from me! The process above goes quite quickly, and I was on shiny new Bicep templates in less than an hour. Bicep is a much friendlier syntax to work with and the IDE support is great. What I also enjoyed is cleaning up all the errors that were present in my ARM templates that would have remained if not for the migration.&lt;/p&gt;

&lt;p&gt;There is a neat comparison of ARM syntax vs Bicep syntax here which highlights a lot of the constructs that become simpler as well: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax"&gt;https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/compare-template-syntax&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy the migration! I will be playing around with modules and reuse next and will share what I find.&lt;/p&gt;

&lt;p&gt;Featured image background by &lt;a href="https://unsplash.com/@jannerboy62?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Nick Fewings&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/bird-migrating?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>bicep</category>
      <category>arm</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Azure Pipelines and Dependabot</title>
      <dc:creator>Richard Roché</dc:creator>
      <pubDate>Mon, 31 May 2021 09:21:36 +0000</pubDate>
      <link>https://dev.to/rickroche/azure-pipelines-and-dependabot-4fb1</link>
      <guid>https://dev.to/rickroche/azure-pipelines-and-dependabot-4fb1</guid>
      <description>&lt;p&gt;Keeping your dependencies up to date in a project is a really easy way to try and keep the software secure. New releases of a dependency often include&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Patches for security vulnerabilities!&lt;/li&gt;
&lt;li&gt;Performance improvements!&lt;/li&gt;
&lt;li&gt;Awesome new features!&lt;/li&gt;
&lt;li&gt;Bug fixes!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It can also be quite a boring activity and can be time-consuming for a team maintaining the project to run updates regularly into production. Fortunately tools like &lt;a href="https://dependabot.com/"&gt;Dependabot&lt;/a&gt; exist!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependabot?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dependabot.com/"&gt;Dependabot&lt;/a&gt; creates pull requests on your repos with the dependencies you should update. You can read about &lt;a href="https://dependabot.com/#how-it-works"&gt;how it works&lt;/a&gt;; but in a nutshell&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it checks for updates of your dependencies&lt;/li&gt;
&lt;li&gt;it opens up a pull request on your repo&lt;/li&gt;
&lt;li&gt;you review the PR and merge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an awesome tool to save you time and I find it makes keeping your dependencies up to date really easy. &lt;/p&gt;

&lt;h2&gt;
  
  
  Integration with Azure Pipelines
&lt;/h2&gt;

&lt;p&gt;Dependabot is baked into the &lt;a href="https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates"&gt;GitHub ecosystem&lt;/a&gt; and really easy to use there. Recently I needed to solve this problem on a project in &lt;a href="https://azure.microsoft.com/en-us/services/devops/"&gt;Azure DevOps&lt;/a&gt; using &lt;a href="https://azure.microsoft.com/en-us/services/devops/pipelines/"&gt;Azure Pipelines&lt;/a&gt; and thought I would share my solution.&lt;/p&gt;

&lt;p&gt;If you search the &lt;a href="https://marketplace.visualstudio.com/"&gt;Azure DevOps Extension Marketplace&lt;/a&gt; for Dependabot you will find &lt;a href="https://marketplace.visualstudio.com/items?itemName=tingle-software.dependabot"&gt;this extension&lt;/a&gt; made by &lt;a href="https://tingle.software/"&gt;Tingle Software&lt;/a&gt;. It is always great to find extensions to speed up integration and I gave this one a whirl.&lt;/p&gt;

&lt;p&gt;The extension is feature rich and works really well with little configuration required. Simply add the below to an Azure Pipeline and run it, and you'll end up with a host of PR's!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckDependencies&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependencies'&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dependabot&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
        &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dependabot@1&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;packageManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nuget'&lt;/span&gt; &lt;span class="c1"&gt;# Examples: nuget, maven, gradle, npm, etc. Add multiple tasks if multiple package managers are used in your solution&lt;/span&gt;
              &lt;span class="na"&gt;targetBranch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt;
              &lt;span class="na"&gt;openPullRequestsLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# Limits the number of PR's you get&lt;/span&gt;
              &lt;span class="na"&gt;setAutoComplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Saves us one click, once our PR policies pass, the update will merge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Have a look at all the &lt;a href="https://github.com/tinglesoftware/dependabot-azure-devops/blob/main/src/extension/README.md#task-parameters"&gt;Task Parameters&lt;/a&gt; that the extension supports to fine tune your implementation. The above checks for &lt;code&gt;nuget&lt;/code&gt; dependencies on the &lt;code&gt;main&lt;/code&gt; branch, limits the number of PR's raise to be 10 and sets the PR to autocomplete (letting our branch policies and PR validation pipelines perform all their checks).&lt;/p&gt;

&lt;h3&gt;
  
  
  Work Item Linking
&lt;/h3&gt;

&lt;p&gt;In the project I'm working in we have a policy set on all PR's to ensure they are linked to a work item. When I ran the above pipeline, I ended up with 10 PR's, none of which were linked to a work item so I couldn't quickly review and merge each one. The extension handles this by allowing you to pass in a &lt;code&gt;workItemId&lt;/code&gt; parameter. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Changes in release 0.5&lt;br&gt;
In the upcoming 0.5 release of the extension, the &lt;code&gt;workItemId&lt;/code&gt; parameter has been &lt;a href="https://github.com/tinglesoftware/dependabot-azure-devops/pull/130"&gt;renamed&lt;/a&gt; to &lt;code&gt;milestone&lt;/code&gt; so please be on the look out for this change!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Microsoft team have an &lt;a href="https://marketplace.visualstudio.com/items?itemName=mspremier.CreateWorkItem"&gt;extension for creating work items&lt;/a&gt; which is really configurable. For my teams workflow, we wanted to have a User Story on our board with all the PR's linked to it. We also didn't want the pipeline creating duplicate User Stories every time it runs if we already had one open that we are working on. After a bit of trial and error, adding the below to the pipeline gave us the desired result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CreateWorkItem@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Story&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workItemType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Story'&lt;/span&gt; &lt;span class="c1"&gt;# We wanted a User Story created, but all work item types are available&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependencies'&lt;/span&gt; &lt;span class="c1"&gt;# The title of the work item&lt;/span&gt;
    &lt;span class="c1"&gt;# Adding some tags to the work item&lt;/span&gt;
    &lt;span class="na"&gt;fieldMappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Tags=dependabot; dependencies &lt;/span&gt;
    &lt;span class="na"&gt;areaPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your\area'&lt;/span&gt;
    &lt;span class="na"&gt;iterationPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your\iteration'&lt;/span&gt;
    &lt;span class="c1"&gt;# Adding duplicate detection, using the area path, iteration path and title to match&lt;/span&gt;
    &lt;span class="na"&gt;preventDuplicates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;keyFields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;System.AreaPath&lt;/span&gt;
      &lt;span class="s"&gt;System.IterationPath&lt;/span&gt;
      &lt;span class="s"&gt;System.Title&lt;/span&gt;
    &lt;span class="c1"&gt;# Create output variables for the next task to be able use the work item ID&lt;/span&gt;
    &lt;span class="na"&gt;createOutputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;outputVariables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;workItemId=ID&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dependabot@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;packageManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nuget'&lt;/span&gt;
    &lt;span class="na"&gt;targetBranch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt;
    &lt;span class="na"&gt;openPullRequestsLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;workItemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(workItemId)&lt;/span&gt; &lt;span class="c1"&gt;# This uses the output from the above as the work item to link the PR's to&lt;/span&gt;
    &lt;span class="na"&gt;setAutoComplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Caching
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=tingle-software.dependabot"&gt;Dependabot extension&lt;/a&gt; depends on a Docker image hosted on &lt;a href="https://hub.docker.com/r/tingle/dependabot-azure-devops/tags?page=1&amp;amp;ordering=last_updated"&gt;Docker Hub&lt;/a&gt;. The image is around &lt;code&gt;4.4GB&lt;/code&gt; in size and can take some time to download in the pipeline.&lt;/p&gt;

&lt;p&gt;In the docs it mentions &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Since this task makes use of a docker image, it may take time to install the docker image. The user can choose to speed this up by using &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops#docker-images"&gt;Caching for Docker&lt;/a&gt; in Azure Pipelines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The caching tasks can look a bit confusing, breaking it down you need 3 parts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker | "${{ variables.imageToCache }}"&lt;/span&gt; &lt;span class="c1"&gt;# image to look for in the cache, e.g. tingle/dependabot-azure-devops:0.4&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(Pipeline.Workspace)/docker&lt;/span&gt; &lt;span class="c1"&gt;# path of the cache&lt;/span&gt;
    &lt;span class="na"&gt;cacheHitVar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DOCKER_CACHE_HIT&lt;/span&gt; &lt;span class="c1"&gt;# variable to set to true if a cache hit is found&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache Docker images&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks if there is a cached version of the image to be used. If yes, &lt;code&gt;DOCKER_CACHE_HIT&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, otherwise &lt;code&gt;false&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;docker load -i $(Pipeline.Workspace)/docker/cache.tar&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore Docker image&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(not(canceled()), eq(variables.DOCKER_CACHE_HIT, 'true'))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we have a cached version of the image, we need to pipeline to download it. This step only runs if &lt;code&gt;DOCKER_CACHE_HIT&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt; and will download the cached archive and load it into the docker context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;mkdir -p $(Pipeline.Workspace)/docker&lt;/span&gt;
    &lt;span class="s"&gt;docker pull -q ${{ parameters.imageToCache }}&lt;/span&gt;
    &lt;span class="s"&gt;docker save -o $(Pipeline.Workspace)/docker/cache.tar ${{ parameters.imageToCache }}&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Save Docker image&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(not(canceled()), or(failed(), ne(variables.DOCKER_CACHE_HIT, 'true')))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we do not have a cached version of the image we need to pull it down from Docker Hub and save it as an archive to be cached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing up
&lt;/h2&gt;

&lt;p&gt;We have a pipeline that runs on a schedule, running all the above steps together. A complete example can be found on GitHub &lt;a href="https://gist.github.com/rick-roche/c083ba72e9acd7635cff3c2757ee04bf"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;schedules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Weekly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Run'&lt;/span&gt;
    &lt;span class="na"&gt;always&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;imageToCache&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tingle/dependabot-azure-devops:0.4&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckDependencies&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependencies'&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dependabot&lt;/span&gt;
        &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
        &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ubuntu-latest'&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CreateWorkItem@1&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Story&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;workItemType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Story'&lt;/span&gt;
              &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependencies'&lt;/span&gt;
              &lt;span class="na"&gt;fieldMappings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
                &lt;span class="s"&gt;Tags=dependabot; dependencies&lt;/span&gt;
              &lt;span class="na"&gt;areaPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your\area'&lt;/span&gt;
              &lt;span class="na"&gt;iterationPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your\iteration'&lt;/span&gt;
              &lt;span class="na"&gt;preventDuplicates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;keyFields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
                &lt;span class="s"&gt;System.AreaPath&lt;/span&gt;
                &lt;span class="s"&gt;System.IterationPath&lt;/span&gt;
                &lt;span class="s"&gt;System.Title&lt;/span&gt;
              &lt;span class="na"&gt;createOutputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;outputVariables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
                &lt;span class="s"&gt;workItemId=ID&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache@2&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker | "${{ variables.imageToCache }}"&lt;/span&gt;
              &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(Pipeline.Workspace)/docker&lt;/span&gt;
              &lt;span class="na"&gt;cacheHitVar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DOCKER_CACHE_HIT&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache Docker images&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;docker load -i $(Pipeline.Workspace)/docker/cache.tar&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore Docker image&lt;/span&gt;
            &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(not(canceled()), eq(variables.DOCKER_CACHE_HIT, 'true'))&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;mkdir -p $(Pipeline.Workspace)/docker&lt;/span&gt;
              &lt;span class="s"&gt;docker pull -q ${{ variables.imageToCache }}&lt;/span&gt;
              &lt;span class="s"&gt;docker save -o $(Pipeline.Workspace)/docker/cache.tar ${{ variables.imageToCache }}&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Save Docker image&lt;/span&gt;
            &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(not(canceled()), or(failed(), ne(variables.DOCKER_CACHE_HIT, 'true')))&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dependabot@1&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependabot'&lt;/span&gt;
            &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;packageManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nuget'&lt;/span&gt;
              &lt;span class="na"&gt;targetBranch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;main'&lt;/span&gt;
              &lt;span class="na"&gt;openPullRequestsLimit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
              &lt;span class="na"&gt;workItemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(workItemId)&lt;/span&gt;
              &lt;span class="na"&gt;setAutoComplete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary the pipeline does the following for you&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs on a weekly schedule (update the cron to suite your needs)&lt;/li&gt;
&lt;li&gt;Creates a new work item, with the tags we would like (avoiding duplicates)&lt;/li&gt;
&lt;li&gt;Tries to use a cached version of the image for faster builds (creating a cached version if not found)&lt;/li&gt;
&lt;li&gt;Run Dependabot, limiting the number of open PR's to 10, linking the PR's to the created work item and completing the PR once all the policies pass&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hopefully this will help you to get your projects running in Azure DevOps nice and up to date!&lt;/p&gt;

</description>
      <category>dependabot</category>
      <category>azure</category>
      <category>devops</category>
      <category>azurepipelines</category>
    </item>
  </channel>
</rss>
