<?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>Forem: Peter O'Connor</title>
    <description>The latest articles on Forem by Peter O'Connor (@poc275).</description>
    <link>https://forem.com/poc275</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%2F623308%2F867c3418-4b02-41c7-90cc-89a82b65bbf8.jpeg</url>
      <title>Forem: Peter O'Connor</title>
      <link>https://forem.com/poc275</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/poc275"/>
    <language>en</language>
    <item>
      <title>Connecting to Azure SQL using Managed Identity</title>
      <dc:creator>Peter O'Connor</dc:creator>
      <pubDate>Sun, 11 Sep 2022 12:43:06 +0000</pubDate>
      <link>https://forem.com/poc275/connecting-to-azure-sql-using-managed-identity-3cji</link>
      <guid>https://forem.com/poc275/connecting-to-azure-sql-using-managed-identity-3cji</guid>
      <description>&lt;p&gt;Connecting your Azure App Service Apps to an Azure SQL database using managed identity makes your app more secure as it eliminates secrets from your app such as credentials in connection strings. This post describes how to set this up. It is mostly taken from the &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/tutorial-connect-msi-sql-database?tabs=windowsclient%2Cef%2Cdotnet"&gt;official docs&lt;/a&gt; but with some extra information and caveats which I found useful to understand the process, hopefully others will too.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Enable AAD authentication on the Azure SQL Database
&lt;/h2&gt;

&lt;p&gt;To be able to use managed identity authentication you need to assign an AAD user (or group) as the admin of the server. This &lt;strong&gt;cannot&lt;/strong&gt; be a Microsoft account that you used to sign up for your Azure subscription. It must be a user that was created in AAD. For testing locally, you can just create a new user in AAD, but remember to sign-in as them and change the default password to prevent any expired password errors.&lt;/p&gt;

&lt;p&gt;To set AAD authentication via the Azure portal go to the SQL Server resource &amp;gt; Azure Active Directory (under Settings) &amp;gt; Set admin (select the AAD user or group) &amp;gt; Save.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Modify the code to request a token
&lt;/h2&gt;

&lt;p&gt;Your code must be modified so that it requests a token for SQL Database access from AAD which it adds to the database connection. Use the &lt;code&gt;Azure.Identity.DefaultAzureCredential&lt;/code&gt; class here as it is flexible enough to work locally on dev and in prod. When running in App Service, it uses the app's system-assigned managed identity. When running locally, it can get a token using the logged-in identity of Visual Studio, VS Code, Azure CLI or Azure PS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be careful during local development using &lt;code&gt;DefaultAzureCredential&lt;/code&gt; as the user you are signed in as in Visual Studio or VS Code might not have access to the database, as it is likely you won't be signed in as the AAD server admin you set up in step 1. In this case you can simply repeat step 4 below for the identity you are signed in as.&lt;/p&gt;

&lt;p&gt;When running locally using &lt;code&gt;DefaultAzureCredential&lt;/code&gt; I also saw some errors to do with expired refresh tokens. Re-signing in inside Visual Studio or Azure CLI (&lt;code&gt;az login&lt;/code&gt;) should fix this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The code to request a token usually goes in your &lt;code&gt;DbContext&lt;/code&gt; object if using Entity Framework. The code example below is for an ASP.NET Framework app using Entity Framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyDatabaseContext&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name=MyDbConnection"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SqlClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TokenRequestContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"https://database.windows.net/.default"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note &lt;code&gt;https://database.windows.net/.default&lt;/code&gt; is the token endpoint for Azure SQL Databases. This string will be different if using Azure Database for MySQL (&lt;code&gt;https://ossrdbms-aad.database.windows.net&lt;/code&gt;), or Azure Database for PostgreSQL (&lt;code&gt;https://ossrdbms-aad.database.windows.net&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Update the connection string
&lt;/h2&gt;

&lt;p&gt;Update the connection string to use AAD managed identity authentication instead of a username/password. This is the whole point of using the managed identity mechanism!&lt;/p&gt;

&lt;p&gt;In either your &lt;code&gt;web.config&lt;/code&gt;, App Service app settings, or both depending on your setup, replace the connection string with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server=tcp:&amp;lt;server-name&amp;gt;.database.windows.net;database=&amp;lt;db name&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how you only need the &lt;code&gt;server&lt;/code&gt; and &lt;code&gt;database&lt;/code&gt; parts. The authentication method is inferred to be AAD managed identity. Also, you don't need to specify a user id (&lt;code&gt;User ID&lt;/code&gt; or &lt;code&gt;UID&lt;/code&gt;) if using system-assigned managed identities (if using a user-assigned managed identity then you would need to specify the user id). This is because AAD authentication for Azure SQL uses a &lt;strong&gt;contained user&lt;/strong&gt;, which differs from the traditional SQL Server approach of having both a server admin login &lt;strong&gt;and&lt;/strong&gt; a database user. More information on this available here: &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/security/contained-database-users-making-your-database-portable?view=sql-server-ver16"&gt;https://docs.microsoft.com/en-us/sql/relational-databases/security/contained-database-users-making-your-database-portable?view=sql-server-ver16&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Configure App Service app to connect to SQL Database using managed identity
&lt;/h2&gt;

&lt;p&gt;The final step is to grant permissions to the App Service's managed identity to access the SQL Database. We do this by adding the managed identity as a user in the database.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Your App Service app must have a managed identity by this point. Create one if it doesn't already exist. E.g. &lt;code&gt;az webapp identity assign --resource-group ... --name &amp;lt;app-name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following SQL commands to grant the &lt;strong&gt;minimum&lt;/strong&gt; permissions your app needs, e.g. (note that the square brackets are required):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="n"&gt;PROVIDER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;db_datareader&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;MEMBER&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;db_datawriter&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;MEMBER&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;em&gt;identity-name&lt;/em&gt; is the name of the managed identity in AAD. If it's a system-assigned identity, the name is &lt;strong&gt;always the same as the name of your App Service app&lt;/strong&gt;. For an AAD group, use the group's display name.&lt;/p&gt;

&lt;p&gt;For a detailed list of available roles see here: &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver16#fixed-database-roles"&gt;https://docs.microsoft.com/en-us/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver16#fixed-database-roles&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To run the SQL commands above you can sign in to the SQL Database via the Azure Cloud Shell using your AAD server admin's credentials created in step 1. E.g:&lt;br&gt;
&lt;code&gt;sqlcmd -S &amp;lt;server-name&amp;gt;.database.windows.net -d &amp;lt;db-name&amp;gt; -U &amp;lt;aad-user-name&amp;gt; -P "&amp;lt;aad-password&amp;gt;" -G -l 30&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, publish your app with the code changes made in step 2 to Azure and you should now be successfully connecting to the database via managed identity with no secrets in sight. I'll drink to that ☕.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>database</category>
    </item>
    <item>
      <title>Streaming Videos from Azure Blob Storage</title>
      <dc:creator>Peter O'Connor</dc:creator>
      <pubDate>Fri, 22 Apr 2022 12:32:45 +0000</pubDate>
      <link>https://forem.com/poc275/streaming-videos-from-azure-blob-storage-10gj</link>
      <guid>https://forem.com/poc275/streaming-videos-from-azure-blob-storage-10gj</guid>
      <description>&lt;p&gt;I recently had a project where I needed to stream video content from an Azure Blob Storage account. Should be easy enough right? Just plonk a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag in and point it to the Blob URL (the blobs are publicly accessible). Well, it wasn't quite so straightforward. Here are some gotchas that might help you if you need to do something similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why are we waiting?
&lt;/h2&gt;

&lt;p&gt;For my first naïve attempt, a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag pointing to the Blob URL, it turned out it was trying to download the complete video file first before playing, and the videos I wanted to stream were too large for that to be acceptable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3odowgemimu3a1khsx5q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3odowgemimu3a1khsx5q.jpg" alt="first attempt network response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full video download took 43 seconds, not great...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For streaming, the request needs to specify a range of data at a time instead of the entire video. This is supported via the &lt;code&gt;Accept-Ranges&lt;/code&gt; HTTP header which returns a HTTP 206 &lt;em&gt;Partial Content&lt;/em&gt; response. Support for this didn't come in until &lt;a href="https://docs.microsoft.com/en-us/rest/api/storageservices/version-2011-08-18" rel="noopener noreferrer"&gt;version '2011-08-18'&lt;/a&gt; of the Azure Storage service which is why the entire file was being downloaded first. You can specify the version when using an SDK or the REST API (via the &lt;code&gt;x-ms-version&lt;/code&gt; header), but for public anonymous access where I wanted to point a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; tag at a URL, this wasn't an option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set a global default version
&lt;/h3&gt;

&lt;p&gt;Instead, you can set the default version globally at the account level using either one of the SDKs or the &lt;a href="https://docs.microsoft.com/en-us/rest/api/storageservices/set-blob-service-properties" rel="noopener noreferrer"&gt;'Set Blob Service Properties'&lt;/a&gt; REST API method. This is the part that probably isn't explained fully in the docs, particularly for public anonymous access, although it is &lt;a href="https://docs.microsoft.com/en-us/rest/api/storageservices/versioning-for-the-azure-storage-services#requests-via-anonymous-access" rel="noopener noreferrer"&gt;hinted at here&lt;/a&gt;. Make a REST/SDK call setting the version to &lt;code&gt;2011-08-18&lt;/code&gt; or later and your storage account will now support range requests and streaming will work as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;blobServiceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperties&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;defaultServiceVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2021-04-10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Set Properties response&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Set Properties error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;How to set the default service version via the JavaScript SDK.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzysaa8fnemlred9o7fw3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzysaa8fnemlred9o7fw3.jpg" alt="streaming network response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With the correct default storage version set, the video can start playing immediately (note the response status code for the video).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: To set the default version you will probably need a &lt;code&gt;PUT&lt;/code&gt; CORS rule on your storage account and a role assignment for the identity making the call. &lt;code&gt;Storage Account Contributor&lt;/code&gt; should do the trick.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Bonus&lt;/strong&gt;: The partial content response will also let your users scrub the video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content Type
&lt;/h2&gt;

&lt;p&gt;Another gotcha to be aware of is to make sure your blobs' content types are set correctly. This is usually set appropriately during upload but ensure it is a &lt;code&gt;video/...&lt;/code&gt; type and hasn't defaulted to something like &lt;code&gt;application/octet-stream&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Info
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/rest/api/storageservices/versioning-best-practices" rel="noopener noreferrer"&gt;General Azure Storage versioning best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
