<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Juice Blog 🧃]]></title><description><![CDATA[I am a software engineer with over a decade of experience building software, engineering platforms, and managing people and teams.]]></description><link>https://juiceblog.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 22:58:19 GMT</lastBuildDate><atom:link href="https://juiceblog.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[This is a test to test RSS connection to dev.to]]></title><description><![CDATA[That’s all.]]></description><link>https://juiceblog.dev/this-is-a-test-to-test-rss-connection-to-devto</link><guid isPermaLink="true">https://juiceblog.dev/this-is-a-test-to-test-rss-connection-to-devto</guid><dc:creator><![CDATA[Justin J Stark]]></dc:creator><pubDate>Tue, 21 Oct 2025 20:07:22 GMT</pubDate><content:encoded><![CDATA[<p>That’s all.</p>
]]></content:encoded></item><item><title><![CDATA[JotForm API and Workspaces]]></title><description><![CDATA[If you are using JotForm Enterprise with Workspaces and you want to query forms for a workspace, you have to use the header jf-team-id.
Getting the Team ID
If you aren’t sure of your team ID, you can issue a GET request to:  
https://{{your-subdomain...]]></description><link>https://juiceblog.dev/jotform-api-and-workspaces</link><guid isPermaLink="true">https://juiceblog.dev/jotform-api-and-workspaces</guid><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Justin J Stark]]></dc:creator><pubDate>Mon, 29 Sep 2025 19:57:42 GMT</pubDate><content:encoded><![CDATA[<p>If you are using JotForm Enterprise with Workspaces and you want to query forms for a workspace, you have to use the header <code>jf-team-id</code>.</p>
<h2 id="heading-getting-the-team-id">Getting the Team ID</h2>
<p>If you aren’t sure of your team ID, you can issue a GET request to:  </p>
<p><code>https://{{your-subdomain}}.jotform.com/API/team/user/me?apiKey={{your-api-key}}</code></p>
<p>You can alternatively put the API key in the header with key <code>APIKEY</code>.</p>
<p>The response you will get looks like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"responseCode"</span>: <span class="hljs-number">200</span>,
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"success"</span>,
    <span class="hljs-attr">"content"</span>: [
        {
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"123456789"</span>,
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Team 1"</span>,
            <span class="hljs-attr">"teamspace_id"</span>: <span class="hljs-string">"1212121212121212"</span>,
            <span class="hljs-attr">"creator"</span>: <span class="hljs-string">"me"</span>,
            <span class="hljs-attr">"owner"</span>: <span class="hljs-string">"me"</span>,
            <span class="hljs-attr">"slug"</span>: <span class="hljs-string">"team1"</span>,
            <span class="hljs-attr">"created_at"</span>: <span class="hljs-string">"2025-09-29 18:54:56"</span>,
            <span class="hljs-attr">"updated_at"</span>: <span class="hljs-literal">null</span>
        }
    ]
}
</code></pre>
<p>The <code>id</code>, in this case <code>123456789</code>, is the ID you will pass in the <code>jf-team-id</code> in future requests. You want the <code>id</code> and not the <code>teamspace_id</code>.</p>
<h2 id="heading-making-a-request">Making a Request</h2>
<p>As an example, to get forms in this workspace, make a GET request to:</p>
<p><code>https://{{your-subdomain}}.jotform.com/API/user/forms</code></p>
<p>with the headers</p>
<pre><code class="lang-json">APIKEY: {{your-api-key}}
jf-team-id: {{id-from-above}}
</code></pre>
<p>And you will get back the forms for this workspace.</p>
]]></content:encoded></item><item><title><![CDATA[The Azure User-Assigned Managed Identity Bible]]></title><description><![CDATA[User-Assigned Managed Identity in Azure is a powerful tool when connecting Azure resources and controlling permissions. But using it across applications requires specific code and configuration. This guide is a consolidation of my learnings and pract...]]></description><link>https://juiceblog.dev/azure-user-assigned-managed-identity-bible</link><guid isPermaLink="true">https://juiceblog.dev/azure-user-assigned-managed-identity-bible</guid><category><![CDATA[Software Engineering]]></category><category><![CDATA[Azure]]></category><dc:creator><![CDATA[Justin J Stark]]></dc:creator><pubDate>Fri, 05 Sep 2025 16:10:43 GMT</pubDate><content:encoded><![CDATA[<p>User-Assigned Managed Identity in Azure is a powerful tool when connecting Azure resources and controlling permissions. But using it across applications requires specific code and configuration. This guide is a consolidation of my learnings and practical applications.</p>
<h2 id="heading-why-user-assigned-managed-identity">Why User-Assigned Managed Identity?</h2>
<h3 id="heading-role-based-access-control">Role-Based Access Control</h3>
<p>Using Managed Identities allows us to control permissions by role. This means we can grant specific roles to specific applications, just like how we grant specific roles to our developers.</p>
<h3 id="heading-singular-permissions-controls-across-resources">Singular Permissions Controls Across Resources</h3>
<p>User-Assigned Managed Identities (UAMIs) allow us to control permissions for multiple resources at once. While granular permissions per resource generally make sense, where this provides the most benefit is with deployment slots. Instead of having to manage a separate managed identity per deployment slot and controlling permissions for each, we can assign one UAMI to our app and all its deployment slots.</p>
<p>Alternatively, with System-Assigned Managed Identities (SAMIs), we get a different Entra User for every single deployment slot and every different resource. This means an application with a web app resource and a function app resource, each with 5 deployment slots, would have 12 separate users we have to manage permissions for.</p>
<h3 id="heading-no-keys-to-manage-and-rotate">No Keys to Manage and Rotate</h3>
<p>When using secrets, managing those secrets is a security concern. Depending on the secret type, Azure might also force the keys to expire, and they will need to be rotated regularly. Without manual reminders, the secrets expire and our apps break.</p>
<p>UAMIs allow us to assign permissions without the need for keys. Once a UAMI is set up and access is configured, it will never expire.</p>
<h3 id="heading-tradeoffs">Tradeoffs</h3>
<p>Using UAMIs can be more difficult:</p>
<ul>
<li><p>You must manually create and enable UAMIs (unless automated by IaC like Bicep or Terraform). They are not enabled by default and not as easy to configure and use as System-Assigned Managed Identity.</p>
</li>
<li><p>You can have both system-defined and user-defined identities or multiple user-defined identities. So, you often have to tell your application/resource which identity to use.</p>
</li>
<li><p>If you want to access Key Vault using UAMI, you must set <code>keyVaultReferenceIdentity</code> on each resource.</p>
</li>
<li><p>For SQL connections, you must set a User ID in the connection string which maps to the Client ID of the managed identity.</p>
</li>
</ul>
<h2 id="heading-azure-setup">Azure Setup</h2>
<h3 id="heading-creating-a-managed-identity">Creating a Managed Identity</h3>
<p>In the Azure Portal, go to Managed Identities. Create a new Managed Identity. Give it a name per resource and environment. Put it in the same resource group as your application.</p>
<h3 id="heading-turning-on-user-assigned-identity">Turning On User-Assigned Identity</h3>
<p>On the application resource, go to Settings -&gt; Identity and turn on User-Assigned Identity and assign the UAMI you created above.</p>
<p>Turn System-Assigned Identity off if it is on.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🚨</div>
<div data-node-type="callout-text"><strong>Be careful with this.</strong> If SAMI was previously turned on and used for Azure resource permissions or SQL connections, turning it off will remove those permissions. Make sure you enable User-Assigned Identity and get everything running with it before turning off System-Assigned Identity.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">🚨</div>
<div data-node-type="callout-text">If you turn System-Assigned Identity off and back on, it will create a new Managed Identity with a new Client ID, so you will have to reconfigure its resource or SQL permissions.</div>
</div>

<h2 id="heading-app-configurations">App Configurations</h2>
<h3 id="heading-sql-connections">SQL Connections</h3>
<p>SQL connection strings must include a <code>User ID</code> parameter. The value should be the <code>Client ID</code> (not the <code>Object ID</code>) of the UAMI.</p>
<p>Ex: <code>Server=&lt;server&gt;; Authentication=Active Directory Managed Identity;Database=&lt;database_name&gt;;User Id=&lt;uami_client_id&gt;</code></p>
<p>Assigning login and access to the SQL server and database is the same as any other user. The Managed Identity is just an Entra user.</p>
<p>One note: In many SQL logs, the user will not show up with its display name. Instead, you will see its <code>Client ID</code> which is a GUID.</p>
<h3 id="heading-application-insights">Application Insights</h3>
<p>Give the UAMI the following access to the Application Insights resource.</p>
<ul>
<li>Monitoring Metrics Publisher</li>
</ul>
<p>This works for the Open Telemetry SDK with Azure Monitor as well as the Application Insights SDK.</p>
<p>References</p>
<ul>
<li><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/azure-ad-authentication?tabs=aspnetcore#configure-and-enable-microsoft-entra-id-based-authentication">MS: Role Based Access for Application Insights</a></li>
</ul>
<h3 id="heading-azure-functions-web-jobs-storage">Azure Functions (Web Jobs Storage)</h3>
<p>Function App Settings:</p>
<pre><code class="lang-ini"><span class="hljs-attr">AzureWebJobsStorage__accountName</span> = &lt;storage_account_name&gt;
<span class="hljs-attr">AzureWebJobsStorage__credential</span> = managedIdentity
<span class="hljs-attr">AzureWebJobsStorage__clientId</span> = &lt;uami_client_id&gt;
</code></pre>
<p>Roles Needed on the Storage Account Resource:</p>
<ul>
<li><p>Storage Blob Data Owner</p>
</li>
<li><p>Storage Account Contributor</p>
<ul>
<li>required if using storage-queue-triggered functions</li>
</ul>
</li>
<li><p>Storage Queue Data Contributor</p>
<ul>
<li>required if using storage-queue-triggered functions</li>
</ul>
</li>
<li><p>Storage Table Data Contributor</p>
<ul>
<li>this one is not in Microsoft’s docs, but if you don’t add it, the function app will regularly throw an exception <code>Azure.RequestFailedException at Azure.Data.Tables.TableRestClient</code> with trace message <code>Error occurred when attempting to purge previous diagnostic event versions</code></li>
</ul>
</li>
</ul>
<p>References</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-identity-based-connections-tutorial">MS: Tutorial: Create a function app that connects to Azure services using identities instead of secrets</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-identity-based-connections-tutorial#grant-the-system-assigned-identity-access-to-the-storage-account">MS: Role Based Access for Azure Functions (Web Jobs)</a></p>
</li>
</ul>
<h3 id="heading-service-bus-message-publishing">Service Bus Message Publishing</h3>
<p>You will need to add <code>Azure.Identity</code> to projects that use <code>DefaultAzureCredential</code> or you can get a runtime error.</p>
<p>Example:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> serviceBusNamespace = <span class="hljs-string">"&lt;service-bus-namespace&gt;"</span>;
<span class="hljs-keyword">var</span> managedIdentityClientId = <span class="hljs-string">"&lt;uami-client-id&gt;"</span>;

<span class="hljs-keyword">var</span> credential = <span class="hljs-keyword">new</span> DefaultAzureCredential(<span class="hljs-keyword">new</span> DefaultAzureCredentialOptions 
{
    ManagedIdentityClientId = managedIdentityClientId
});

<span class="hljs-keyword">await</span> <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> serviceBusClient = <span class="hljs-keyword">new</span> ServiceBusClient(serviceBusNamespace, credential);

<span class="hljs-keyword">await</span> <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> sender = serviceBusClient.CreateSender(QUEUE_NAME);

<span class="hljs-keyword">await</span> sender.SendMessageAsync(message);
</code></pre>
<p>Roles Needed on the Service Bus Resource:</p>
<ul>
<li><code>Azure Service Bus Data Owner</code></li>
</ul>
<h3 id="heading-service-bus-message-consuming-azure-function-triggers">Service Bus Message Consuming (Azure Function Triggers)</h3>
<p>To run a function whenever a message arrives in a Service Bus Queue or Subscription, you have to set <code>Connection = "MyAppServiceBusConnection"</code> in your <code>ServiceBusTrigger</code>. Then you have to set your <code>local.settings.json</code> and the environment variables on the function app service as:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">"MyAppServiceBusConnection__fullyQualifiedNamespace":</span> <span class="hljs-string">"&lt;servicebusname&gt;.servicebus.windows.net"</span><span class="hljs-string">,</span>
<span class="hljs-attr">"MyAppServiceBusConnection__credential":</span> <span class="hljs-string">"managedIdentity"</span><span class="hljs-string">,</span>
<span class="hljs-attr">"MyAppServiceBusConnection__clientId":</span> <span class="hljs-string">"&lt;clientId&gt;"</span>
</code></pre>
<p>Note if you set these keys in Key Vault or Azure App Config, you have to nest them or use the <code>:</code> delimiter in the keys instead of the <code>__</code> delimiter.</p>
<p>Then you can use an Azure Functions trigger like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">MyFunction</span>(<span class="hljs-params"></span>)</span>
{
    [<span class="hljs-meta">Function(nameof(MyFunction))</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Run</span>(<span class="hljs-params">
        [ServiceBusTrigger(
            topicName: <span class="hljs-string">"%MyTopicName%"</span>, // %% reads the variable <span class="hljs-keyword">from</span> configuration
            subscriptionName: <span class="hljs-string">"%MySubscriptionName%"</span>,
            Connection = <span class="hljs-string">"MyAppServiceConnection"</span></span>)
        ] QueueMessage message)</span>
    {
    }
}
</code></pre>
<p>Roles Needed on the Service Bus Resource:</p>
<ul>
<li><p>Azure Service Bus Data Receiver</p>
</li>
<li><p>Azure Service Bus Data Owner</p>
</li>
</ul>
<p>References</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger">Azure Service Bus trigger for Azure Functions</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&amp;pivots=programming-language-csharp#grant-permission-to-the-identity">Role Based Access for Service Bus Triggers</a></p>
</li>
</ul>
<h3 id="heading-storage-account-queue-publishing">Storage Account Queue Publishing</h3>
<p>You will need to add <code>Azure.Identity</code> to projects that use <code>DefaultAzureCredential</code> or you can get a runtime error.</p>
<p>Example:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> queueServiceUri = <span class="hljs-string">"&lt;storage-account-queue-service-uri"</span>);
<span class="hljs-keyword">var</span> queueName = <span class="hljs-string">"&lt;queue-name&gt;"</span>;
<span class="hljs-keyword">var</span> managedIdentityClientId = <span class="hljs-string">"&lt;uami-client-id&gt;"</span>;

<span class="hljs-keyword">var</span> credentials = <span class="hljs-keyword">new</span> DefaultAzureCredential(<span class="hljs-keyword">new</span> DefaultAzureCredentialOptions
{
    ManagedIdentityClientId = managedIdentityClientId
});

<span class="hljs-keyword">var</span> queueClient = <span class="hljs-keyword">new</span> QueueClient(
    <span class="hljs-keyword">new</span> Uri(<span class="hljs-string">$"<span class="hljs-subst">{queueServiceUri}</span>/<span class="hljs-subst">{queueName}</span>"</span>),
    credentials);
</code></pre>
<p>Roles Needed on the Storage Account Resource:</p>
<ul>
<li>Storage Queue Data Contributor</li>
</ul>
<h3 id="heading-storage-account-queue-consuming-azure-function-triggers">Storage Account Queue Consuming (Azure Function Triggers)</h3>
<p>Azure Functions should use UAMI to trigger on new messages to a storage account queue.</p>
<p>These are the settings you will need in your <code>appsettings.json</code> file.</p>
<pre><code class="lang-ini"><span class="hljs-attr">MyStorageConnection__queueServiceUri</span> = https://&lt;storage_account_name&gt;.queue.core.windows.net
<span class="hljs-attr">MyStorageConnection__credential</span> = managedIdentity
<span class="hljs-attr">MyStorageConnection__clientId</span> = &lt;uami_client_id&gt;
</code></pre>
<p>Then you can use an Azure Functions trigger like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">MyFunction</span>(<span class="hljs-params"></span>)</span>
{
    [<span class="hljs-meta">Function(nameof(MyFunction))</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Run</span>(<span class="hljs-params">
        [QueueTrigger(
            <span class="hljs-string">"%MyQueueName%"</span>, // %% reads the variable <span class="hljs-keyword">from</span> configuration
            Connection = <span class="hljs-string">"MyStorageConnection"</span></span>)
        ] QueueMessage message)</span>
    {
    }
}
</code></pre>
<p>Roles Needed on the Storage Account:</p>
<ul>
<li><p>Storage Queue Data Reader</p>
</li>
<li><p>Storage Queue Data Message Processor</p>
</li>
</ul>
<p>References</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger">Azure Queue storage trigger for Azure Functions</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger">MS: Role Based Access for Azure Function Triggers</a></p>
</li>
</ul>
<h2 id="heading-key-vault">Key Vault</h2>
<p>To access Key Vault, the application resource (both web apps and function apps and probably others) needs a special property set. You must set the <code>keyVaultReferenceIdentity</code> to the ARM of the UAMI. There is no way in the Azure Portal UI to do this. Instead, you must use the Azure Cloud Shell or Bicep.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">You must run these commands from the Azure Cloud Shell. Running them locally will error.</div>
</div>

<pre><code class="lang-bash">az account <span class="hljs-built_in">set</span> --subscription <span class="hljs-string">"&lt;subscription_name&gt;"</span>
identityResourceId=$(az identity show --resource-group &lt;resource_group_name&gt; --name &lt;resource_name&gt; --query id -o tsv) 
<span class="hljs-built_in">echo</span> <span class="hljs-variable">${identityResourceId}</span>
az functionapp update --resource-group &lt;resource_group_name&gt; --name &lt;resource_name&gt; --<span class="hljs-built_in">set</span> keyVaultReferenceIdentity=<span class="hljs-string">"<span class="hljs-variable">${identityResourceId}</span>"</span> 
az functionapp update --resource-group &lt;resource_group_name&gt; --name &lt;resource_name&gt; --slot &lt;slot_name&gt; --<span class="hljs-built_in">set</span> keyVaultReferenceIdentity=<span class="hljs-string">"<span class="hljs-variable">${identityResourceId}</span>"</span> 
<span class="hljs-comment">#...</span>
az webapp update     --resource-group &lt;resource_group_name&gt; --name &lt;resource_name&gt; --slot &lt;slot_name&gt; --<span class="hljs-built_in">set</span> keyVaultReferenceIdentity=<span class="hljs-string">"<span class="hljs-variable">${identityResourceId}</span>"</span>
<span class="hljs-comment">#...</span>
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🚧</div>
<div data-node-type="callout-text">This section is still a work-in-progress and needs expanded.</div>
</div>

<h2 id="heading-running-locally">Running Locally</h2>
<p>You cannot use UAMI with Azure function app triggers locally since managed identity only works within Azure.</p>
<p>Instead, you can use <code>azurecli</code> as the credential in <code>local.settings.json</code>.</p>
<pre><code class="lang-json">
{
  <span class="hljs-attr">"Values"</span>: {
    <span class="hljs-attr">"AzureWebJobsStorage__accountName"</span>: <span class="hljs-string">"&lt;storage_account_name&gt;"</span>,
    <span class="hljs-attr">"AzureWebJobsStorage__credential"</span>: <span class="hljs-string">"azurecli"</span>
  }
}
</code></pre>
<p>These must be set in local.settings.json and not in appsettings.json files since they must be available to the Function App host at startup.</p>
<p>That works for Service Bus and Storage Account queue triggers too.</p>
<p>Alternatively, you can specify a full connection string.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Values"</span>: {
    <span class="hljs-attr">"AzureWebJobsStorage"</span>: <span class="hljs-string">"&lt;storage_account_connection_string&gt;"</span>
  }
}
</code></pre>
<p>If you want to use Azurite, a storage emulator, for Web Jobs Storage you can instead set:</p>
<pre><code class="lang-json">
{
  <span class="hljs-attr">"Values"</span>: {
    <span class="hljs-attr">"AzureWebJobsStorage"</span>: <span class="hljs-string">"UseDevelopmentStorage=true"</span>  
  }
}
</code></pre>
]]></content:encoded></item></channel></rss>