<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <title>Azure</title>
    <link href="https://blazorcode.uk/azure/feed.xml" rel="self" />
    <link href="https://blazorcode.uk/azure" />
    <updated>2026-04-19T11:49:15+01:00</updated>
    <author>
        <name>Christopher J Bell</name>
    </author>
    <id>https://blazorcode.uk/azure</id>

    <entry>
        <title>Adding Stripe Keys to Azure Key Vault</title>
        <author>
            <name>Christopher J Bell</name>
        </author>
        <link href="https://blazorcode.uk/azure/adding-stripe-keys-to-azure-key-vault/"/>
        <id>https://blazorcode.uk/azure/adding-stripe-keys-to-azure-key-vault/</id>

        <updated>2026-04-19T10:37:08+01:00</updated>
            <summary type="html">
                <![CDATA[
                    Goal Move this out of appsettings.json: "Stripe": { "SecretKey": "sk_test_XXXX", "PublishableKey": "pk_test_XXXX", "WebhookSecret": "whsec_XXXX" }, …and into Azure Key Vault. Current configuration "Stripe": { "SecretKey":&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                
    <h2 id="goal">
      Goal
    </h2>

  <p>
    Move this out of appsettings.json:
  </p>
<pre class="line-numbers  language-json"><code>  "Stripe": {
    "SecretKey": "sk_test_XXXX",
    "PublishableKey": "pk_test_XXXX",
    "WebhookSecret": "whsec_XXXX"
  },
</code></pre>

  <p>
    …and into Azure Key Vault.
  </p>

    <h2 id="1-which-stripe-values-are-sensitive-and-why">
      1. Which Stripe values are sensitive (and why)
    </h2>

  <p>
    Current configuration
  </p>
<pre class="line-numbers  language-json"><code>  "Stripe": {
    "SecretKey": "sk_test_XXXX",
    "PublishableKey": "pk_test_XXXX",
    "WebhookSecret": "whsec_XXXX"
  },
</code></pre>
<div><table>
    <tr>
        <td><b>Key</b></td>
        <td><b>Sensitive?</b></td>
        <td><b>Why</b></td>
    </tr>
    <tr>
        <td>SecretKey</td>
        <td>✅ Yes</td>
        <td>Full API access (charges, refunds, customers)</td>
    </tr>
    <tr>
        <td>WebhookSecret</td>
        <td>✅ Yes</td>
        <td>Verifies Stripe signatures</td>
    </tr>
    <tr>
        <td>PublishableKey</td>
        <td>⚠️ Technically no</td>
        <td>Intended for browsers, but still environment-specific</td>
    </tr>
</table></div>

  <p>
    Recommendation (pragmatic, consistent, future‑proof):
  </p>

  <p>
    Move all three to Azure Key Vault
  </p>

  <p>
    This avoids:
  </p>

  <ul>
    <li>Accidentally committing live values later</li><li>Environment-mixing mistakes</li><li>Special‑case handling in code</li>
  </ul>

    <h2 id="2-add-stripe-secrets-to-azure-key-vault">
      2. Add Stripe secrets to Azure Key Vault
    </h2>

  <p>
    Go to:
  </p>

  <ul>
    <li>Azure Portal → Key Vault → Objects&nbsp;→  Secrets → Generate/Import</li><li>Create three secrets, using the ASP.NET Core naming convention.</li>
  </ul>

  <ul>
    <li>Secret 1 – <b>Stripe Secret Key</b></li><ul><li><b>Name</b>:&nbsp;Stripe--SecretKey</li><li><b>Value</b>:&nbsp;sk_test_XXXX</li></ul><li>Secret 2 – <b>Stripe Publishable Key</b></li><ul><li><b>Name</b>:&nbsp;Stripe--PublishableKey</li><li><b>Value</b>:&nbsp;pk_test_XXXX</li></ul><li>Secret 3 – <b>Stripe Webhook Secret</b></li><ul><li><b>Name</b>: Stripe--WebhookSecret</li><li><b>Value</b>: whsec_XXXX</li></ul>
  </ul>

    <h3 id="3-remove-stripe-keys-from-appsettingsjson">
      3. Remove Stripe keys from appsettings.json
    </h3>

  <p>
    Update your appsettings/json to remove the section entirely,&nbsp;(or leave "Stripe": { } if you prefer structural clarity)
  </p>

    <h3 id="4-ensure-azure-key-vault-is-loaded-early-same-ordering-rule">
      4. Ensure Azure Key Vault is loaded early (same ordering rule)
    </h3>

  <p>
    In Program.cs:
  </p>
<pre class="line-numbers  language-csharp"><code>var builder = WebApplication.CreateBuilder(args);

// Must come BEFORE any Stripe config is read
builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());
``</code></pre>

    <h3 id="5-bind-stripe-configuration-normally">
      5. Bind Stripe configuration normally
    </h3>

  <p>
    If you already have a settings class, great. If not, create one in Data/Models:
  </p>
<pre class="line-numbers  language-csharp"><code>namespace WebCashFlowV2.Data.Models
{
    public class StripeOptions
    {
        public string SecretKey { get; set; } = default!;
        public string PublishableKey { get; set; } = default!;
        public string WebhookSecret { get; set; } = default!;
    }
}
</code></pre>

  <p>
    Register it in Program.cs
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Services.Configure&lt;StripeOptions&gt;(
    builder.Configuration.GetSection("Stripe"));</code></pre>

  <p>
    ASP.NET Core will now merge:
  </p>

  <ul>
    <li>Non‑existent JSON</li><li>Azure Key Vault secrets</li>
  </ul>

  <p>
    …into one clean StripeOptions object.
  </p>

    <h3 id="step-6-stripeoptions-binding-minimal-changes">
      Step 6 – StripeOptions binding (minimal changes)
    </h3>

  <p>
    You already have this:
  </p>
<pre class="line-numbers  language-csharp"><code>var stripeOptions = builder.Configuration
    .GetSection("Stripe")
    .Get&lt;StripeOptions&gt;() ?? new StripeOptions();</code></pre>

  <p>
    Leave this exactly as-is
  </p>

  <p>
    Once you have these secrets in Key Vault:
  </p>
<pre class="line-numbers  language-html"><code>Stripe--SecretKey
Stripe--PublishableKey
Stripe--WebhookSecret</code></pre>

  <p>
    ASP.NET Core automatically maps them to:
  </p>
<pre class="line-numbers  language-html"><code>Stripe:SecretKey
Stripe:PublishableKey
Stripe:WebhookSecret</code></pre>

  <p>
    …and Get<stripeoptions>() will populate them correctly.</stripeoptions>
  </p>

  <p>
    Add one safety check (recommended)
  </p>

  <p>
    Immediately after binding, add validation:
  </p>
<pre class="line-numbers  language-csharp"><code>if (string.IsNullOrWhiteSpace(stripeOptions.SecretKey) ||
    string.IsNullOrWhiteSpace(stripeOptions.PublishableKey) ||
    string.IsNullOrWhiteSpace(stripeOptions.WebhookSecret))
{
    throw new InvalidOperationException("Stripe configuration is incomplete.");
}</code></pre>

  <p>
    This ensures:
  </p>

  <ul>
    <li>Startup fails fast</li><li>You don’t get mysterious Stripe runtime errors</li><li>Live/test environment mix‑ups are caught immediately</li>
  </ul>

    <h3 id="step-7-register-stripe-with-the-stripe-sdk">
      Step 7 – Register Stripe with the Stripe SDK
    </h3>

  <p>
    This is the one step that often gets muddled, so let’s be explicit.
  </p>

  <p>
    Stripe’s .NET SDK needs the SecretKey registered once at startup.
  </p>

  <p>
    Add after your stripeOptions are loaded and validated:
  </p>
<pre class="line-numbers  language-csharp"><code>Stripe.StripeConfiguration.ApiKey = stripeOptions.SecretKey;</code></pre>

  <ul>
    <li>This replaces any previous hard‑coded Stripe key</li><li>This pulls directly from Azure Key Vault via configuration</li><li>This works for test and live keys without code changes</li>
  </ul>

    <h3 id="step-8-register-services-almost-unchanged">
      Step 8 – Register services (almost unchanged)
    </h3>

  <p>
    Your existing registrations:
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Services.AddSingleton(stripeOptions);
builder.Services.AddSingleton&lt;StripeService&gt;();
``</code></pre>

  <p>
    These stay exactly as they are
  </p>

  <p>
    Your StripeService can now:
  </p>

  <ul>
    <li>Read SecretKey (already set globally)</li><li>Use PublishableKey when needed</li><li>Use WebhookSecret for signature validation</li>
  </ul>

  <p>
    Nothing else needs to change.
  </p>

    <h3 id="final-correct-ordering-in-programcs-complete-example">
      Final, correct ordering in Program.cs (complete example)
    </h3>

  <p>
    Putting it all together, the correct final structure looks like this:
  </p>
<pre class="line-numbers  language-csharp"><code>var builder = WebApplication.CreateBuilder(args);

// 1. Add Azure Key Vault FIRST
builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());

// 2. Bind Stripe configuration
var stripeOptions = builder.Configuration
    .GetSection("Stripe")
    .Get&lt;StripeOptions&gt;() ?? new StripeOptions();

// 3. Validate Stripe configuration
if (string.IsNullOrWhiteSpace(stripeOptions.SecretKey) ||
    string.IsNullOrWhiteSpace(stripeOptions.PublishableKey) ||
    string.IsNullOrWhiteSpace(stripeOptions.WebhookSecret))
{
    throw new InvalidOperationException("Stripe configuration is incomplete.");
}

// 4. Initialise Stripe SDK
Stripe.StripeConfiguration.ApiKey = stripeOptions.SecretKey;

// 5. Register services
builder.Services.AddSingleton(stripeOptions);
builder.Services.AddSingleton&lt;StripeService&gt;();</code></pre>

  <p>
    That’s it.
  </p>

    <h3 id="why-this-is-the-right-solution">
      Why this is the “right” solution
    </h3>

  <p>
    ✅ No change to your existing design<br>✅ No use of IOptions<t> required<br>✅ Compatible with Key Vault, test keys, and live keys<br>✅ Fail‑fast configuration validation<br>✅ Clean separation of concerns</t>
  </p>

    <h3 id="quick-answers-to-likely-follow-up-questions">
      Quick answers to likely follow‑up questions
    </h3>

  <p>
    ❓ Do I need separate code for test vs live Stripe?<br>No. Just change the Key Vault secret values.<br>❓ Should PublishableKey really be in Key Vault?<br>Not strictly required, but strongly recommended to avoid environment mix‑ups.<br>❓ Will this work in Azure App Service?<br>Yes — via Managed Identity.<br>❓ Will this work locally?<br>Yes — via az login.
  </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Adding SQL Connection Strings to Azure Key Vault</title>
        <author>
            <name>Christopher J Bell</name>
        </author>
        <link href="https://blazorcode.uk/azure/adding-sql-connection-strings-to-azure-key-vault/"/>
        <id>https://blazorcode.uk/azure/adding-sql-connection-strings-to-azure-key-vault/</id>

        <updated>2026-04-19T10:19:53+01:00</updated>
            <summary type="html">
                <![CDATA[
                    Goal Move this out of appsettings.json: "ConnectionStrings": { "SqlDBcontext": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WebCashFlow;Integrated Security=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False;Command Timeout=30" } …and into Azure Key&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                
    <h2 id="goal">
      Goal
    </h2>

  <p>
    Move this out of appsettings.json:
  </p>
<pre class="line-numbers  language-json"><code>"ConnectionStrings": {
  "SqlDBcontext": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=WebCashFlow;Integrated Security=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False;Command Timeout=30"
}</code></pre>

  <p>
    …and into Azure Key Vault, without breaking your app
  </p>

    <h2 id="step-by-step-move-sql-connection-string-to-azure-key-vault">
      Step‑by‑step: Move SQL connection string to Azure Key Vault
    </h2>

    <h3 id="step-1-add-the-connection-string-as-a-key-vault-secret">
      Step 1: Add the connection string as a Key Vault secret
    </h3>

  <p>
    Go to:
  </p>

  <ul>
    <li>Azure Portal → Key Vault → Objects&nbsp;→ Secrets → Generate/Import</li><li><b>Name</b>:&nbsp;ConnectionStrings--SqlDBcontext</li><li><b>Value</b>: YourConnectionString</li><li>Click <b>Create</b></li>
  </ul>

    <h3 id="step-2-remove-it-from-appsettingsjson">
      Step 2: Remove it from appsettings.json
    </h3>

  <p>
    Update appsettings.json to remove the connection string completely.
  </p>

    <h3 id="step-3-ensure-key-vault-is-added-early-same-rule-as-before">
      Step 3: Ensure Key Vault is added early (same rule as before)
    </h3>

  <p>
    In Program.cs, make sure this appears before anything calls GetConnectionString(...):
  </p>
<pre class="line-numbers  language-csharp"><code>var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.v</code></pre>

  <p>
    (Already encountered this ordering rule with Syncfusion — it applies here too.)
  </p>

    <h3 id="step-4-leave-your-programcs-configuration-unchanged">
      Step 4: Leave your Program.cs configuration unchanged
    </h3>

  <p>
    Program.cs currently contains:
  </p>
<pre class="line-numbers  language-csharp"><code>var sqlConnectionConfiguration = new SqlConnectionConfiguration(
    builder.Configuration.GetConnectionString("SqlDBcontext")</code></pre>

  <p>
    Leave this unchanged.
  </p>

    <h3 id="step-5-optional-but-recommended-validate-at-startup">
      Step 5: Optional but recommended – validate at startup
    </h3>

  <p>
    Fail fast if the connection string is missing. Insert the following code before the <code>var sqlConnectionConfiguration</code> line.
  </p>
<pre class="line-numbers  language-csharp"><code>var connectionString =
    builder.Configuration.GetConnectionString("SqlDBcontext");

if (string.IsNullOrWhiteSpace(connectionString))
{
    throw new InvalidOperationException(
        "SQL connection string 'SqlDBcontext' not found."
    );
}

var sqlConnectionConfiguration =
    new SqlConnectionConfiguration(connectionString);
</code></pre>

  <p>
    This avoids:
  </p>

  <ul>
    <li>Silent failures</li><li>Confusing SQL exceptions later</li>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="3">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/11/gallery/AzureKeyVault-03-ConnectionStrings-01.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/11/gallery/AzureKeyVault-03-ConnectionStrings-01-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/11/gallery/AzureKeyVault-03-ConnectionStrings-02.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/11/gallery/AzureKeyVault-03-ConnectionStrings-02-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/11/gallery/AzureKeyVault-03-ConnectionStrings-03.png" data-size="847x670">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/11/gallery/AzureKeyVault-03-ConnectionStrings-03-thumbnail.png" height="608" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h3 id="step-6-test-locally">
      Step 6: Test locally
    </h3>

  <ul>
    <li>Make sure you’re logged in</li><ul><li>az login</li></ul><li>Ensure your user has:</li><ul><li>Key Vault Secrets User role</li></ul><li>Run the app</li><li>Confirm</li><ul><li><span style="font-size: 1em;">Data loads correctly</span></li></ul>
  </ul>

    <h2 id="what-happens-later-when-you-move-to-azure-sql">
      What happens later when you move to Azure SQL?
    </h2>

  <p>
    This setup already prepares you perfectly.
  </p>

  <p>
    Today (local DB):
  </p>
<pre class="line-numbers  language-html"><code>(localdb)\MSSQLLocalDB
Integrated Security=True</code></pre>

  <p>
    Future (Azure SQL):
  </p>
<pre class="line-numbers  language-html"><code>Server=tcp:yourserver.database.windows.net;
Database=WebCashFlow;
Authentication=Active Directory Default;
Encrypt=True;</code></pre>

  <p>
    ✅ You will just change the secret value in Key Vault<br>✅ No code changes<br>✅ No config file changes<br>✅ No redeployment required (unless you want)
  </p>

    <h3 id="important-note-future-best-practice">
      Important note (future best practice)
    </h3>

  <p>
    When you switch to Azure SQL, do not use SQL passwords if you can avoid it.
  </p>

  <p>
    Instead:
  </p>

  <ul>
    <li>Use Managed Identity</li><li>Use</li>
  </ul>
<pre class="line-numbers  language-html"><code>Authentication=Active Directory Default</code></pre>

  <ul>
    <li>Grant the Managed Identity access to the database</li>
  </ul>

  <p>
    At that point:
  </p>

  <ul>
    <li>The connection string still lives in Key Vault</li><li>But it contains no password at all</li>
  </ul>

    <h2 id="final-result-after-this-step">
      Final result (after this step)
    </h2>
<div><table>
    <tr>
        <td><b>Item</b></td>
        <td><b>Location</b></td>
    </tr>
    <tr>
        <td>SQL connection string</td>
        <td>Azure Key Vault</td>
    </tr>
    <tr>
        <td>appsettings.json</td>
        <td>No secrets</td>
    </tr>
    <tr>
        <td>EF Core configuration</td>
        <td>Unchanged</td>
    </tr>
    <tr>
        <td>Local auth</td>
        <td>az login</td>
    </tr>
    <tr>
        <td>Azure auth</td>
        <td>Managed Identity</td>
    </tr>
</table></div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Adding SMTP Credentials to Azure Key Vault</title>
        <author>
            <name>Christopher J Bell</name>
        </author>
        <link href="https://blazorcode.uk/azure/adding-smtp-credentials-to-azure-key-vault/"/>
        <id>https://blazorcode.uk/azure/adding-smtp-credentials-to-azure-key-vault/</id>

        <updated>2026-04-18T18:21:25+01:00</updated>
            <summary type="html">
                <![CDATA[
                    Summary The current configuration in appsettings.json looks like this: "Smtp": { "Host": "mail.chrisbell.co.uk", "Port": 587, "UseSsl": true, "FromName": "Website Contact", "FromEmail": "chris@chrisbell.co.uk", "Username": "UserName", "Password":&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                
    <h2 id="summary">
      Summary
    </h2>

  <p>
    The current configuration in appsettings.json looks like this:
  </p>
<pre class="line-numbers  language-json"><code> 
"Smtp": {
  "Host": "mail.chrisbell.co.uk",
  "Port": 587,
  "UseSsl": true,
  "FromName": "Website Contact",
  "FromEmail": "chris@chrisbell.co.uk",
  "Username": "UserName",
  "Password": "Password"
}
</code></pre>

  <p>
    The following should go to Key Vault (sensitive)
  </p>

  <ul>
    <li>UserName</li><li>Password</li>
  </ul>

  <p>
    But these can safely be left in appsettings.json
  </p>

  <ul>
    <li>Host</li><li>Port</li><li>UseSSL</li><li>FromName</li><li>FromEmail</li>
  </ul>

    <h2 id="move-smtp-secrets-to-azure-key-vault">
      Move SMTP secrets to Azure Key Vault
    </h2>

    <h3 id="step-1-add-smtp-secrets-to-azure-key-vault">
      Step 1: Add SMTP secrets to Azure Key Vault
    </h3>

  <ul>
    <li>Azure Portal → Key Vault → Objects → Secrets → Create</li><ul><li>Secret 1 – SMTP Username</li><ul><li>Name:&nbsp;Smtp--Username</li><li>Value: UserName</li></ul><li><span style="font-size: 1em;">Secret 2 – SMTP Password</span></li><ul><li>Name:&nbsp;Smtp--Password</li><li>Value: Actual Password</li></ul></ul>
  </ul>

    <h3 id="step-2-remove-sensitive-fields-from-appsettingsjson">
      Step 2: Remove sensitive fields from appsettings.json
    </h3>

  <p>
    Update appsettings.json to look like:
  </p>
<pre class="line-numbers  language-json"><code>"Smtp": {
  "Host": "mail.chrisbell.co.uk",
  "Port": 587,
  "UseSsl": true,
  "FromName": "Website Contact",
  "FromEmail": "chris@chrisbell.co.uk"
}
</code></pre>

    <h3 id="step-3-ensure-key-vault-is-added-early-already-done">
      Step 3: Ensure Key Vault is added early (already done)
    </h3>

  <p>
    In Program.cs, make sure this is before any services read SMTP config
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());
``</code></pre>

    <h3 id="step-4-bind-smtp-settings-normally-recommended-pattern">
      Step 4: Bind SMTP settings normally (recommended pattern)
    </h3>

  <p>
    Create a settings class if you don’t already have one (save in Data/Models):
  </p>
<pre class="line-numbers  language-csharp"><code>namespace WebCashFlowV2.Data.Models{

    public class SmtpOptions
    {
        public string Host { get; set; } = default!;
        public int Port { get; set; }
        public bool UseSsl { get; set; }
        public string FromName { get; set; } = default!;
        public string FromEmail { get; set; } = default!;
        public string Username { get; set; } = default!;
        public string Password { get; set; } = default!;
    }
}</code></pre>

  <p>
    Register it in Program.cs
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Services.Configure&lt;SmtpOptions&gt;(
    builder.Configuration.GetSection("Smtp"));
</code></pre>

  <p>
    Why this works
  </p>

  <p>
    ASP.NET Core merges configuration from:
  </p>

  <ul>
    <li>appsettings.json</li><li>Azure Key Vault</li>
  </ul>

  <p>
    So that
  </p>

  <ul>
    <li>Non‑sensitive values come from JSON</li><li>Sensitive values come from Key Vault</li><li>Your app code never needs to know the difference</li>
  </ul>

    <h3 id="step-5-validate-at-startup-fail-fast">
      Step 5: Validate at startup (fail fast)
    </h3>

  <p>
    This is optional but strongly recommended: In Program.cs insert:
  </p>
<pre class="line-numbers  language-csharp"><code>var smtpOptions = builder.Configuration.GetSection("Smtp").Get&lt;SmtpOptions&gt;();

if (string.IsNullOrWhiteSpace(smtpOptions?.Username) ||
    string.IsNullOrWhiteSpace(smtpOptions.Password))
{
    throw new InvalidOperationException("SMTP credentials are missing.");
}
</code></pre>

  <ul>
    <li>&nbsp;Catches misconfigurations immediately</li><li>Avoids silent email failures</li>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="3">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/10/gallery/AzureKeyVault-02-SMTP-01.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/10/gallery/AzureKeyVault-02-SMTP-01-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/10/gallery/AzureKeyVault-02-SMTP-02.png" data-size="743x438">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/10/gallery/AzureKeyVault-02-SMTP-02-thumbnail.png" height="438" width="743" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/10/gallery/AzureKeyVault-02-SMTP-03.png" data-size="792x511">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/10/gallery/AzureKeyVault-02-SMTP-03-thumbnail.png" height="496" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h3 id="step-6-test-locally">
      Step 6: Test locally
    </h3>

  <ul>
    <li>Make sure you’re logged in:</li><ul><li>az login</li></ul><li><span style="font-size: 1em;">Ensure your user has:</span></li><ul><li>Key Vault Secrets User role</li></ul><li><span style="font-size: 1em;">Run the app</span></li><li>Send a test email</li>
  </ul>

  <p>
    If it sends → success
  </p>

  <p>
    If it fails → check secret names or ordering
  </p>

    <h3 id="what-happens-in-azure-no-code-changes">
      What happens in Azure (no code changes)
    </h3>

  <ul>
    <li>DefaultAzureCredential switches to Managed Identity</li><li>Username &amp; password come from Key Vault</li><li>JSON stays secret‑free</li><li>Same code, same behaviour</li>
  </ul>

    <h3 id="final-result-clean-andamp-secure">
      Final result (clean &amp; secure)
    </h3>
<div><table>
    <tr>
        <td><b>Item</b></td>
        <td><b>Location</b></td>
    </tr>
    <tr>
        <td>SMTP credentials</td>
        <td>Azure Key Vault</td>
    </tr>
    <tr>
        <td>SMTP host/port</td>
        <td>appsettings.json</td>
    </tr>
    <tr>
        <td>Local authentication</td>
        <td>az login</td>
    </tr>
    <tr>
        <td>Azure authentication</td>
        <td>Managed Identity</td>
    </tr>
    <tr>
        <td>Secrets in source control</td>
        <td>❌ none</td>
    </tr>
</table></div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Adding Syncfusion Licence to Azure Key Vault</title>
        <author>
            <name>Christopher J Bell</name>
        </author>
        <link href="https://blazorcode.uk/azure/adding-syncfusion-licence-to-azure-key-vault/"/>
        <id>https://blazorcode.uk/azure/adding-syncfusion-licence-to-azure-key-vault/</id>

        <updated>2026-04-18T16:55:03+01:00</updated>
            <summary type="html">
                <![CDATA[
                    Summary What we’re going to do (high level) In azure Portal Select Why the --? ASP.NET Core maps: Syncfusion--LicenseKey to: Syncfusion:LicenseKey This allows clean strongly-typed&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                
    <h2 id="summary">
      Summary
    </h2>

  <p>
    What we’re going to do (high level)
  </p>

  <ol>
    <li>Store the Syncfusion licence key <b>as a secret in Azure Key Vault</b></li><li>Make ASP.NET Core <b>read it automatically</b> via AddAzureKeyVault</li><li>Remove the licence key from appsettings.json</li><li>Register the Syncfusion licence <b>at app startup</b></li>
  </ol>

    <h3 id="step-1-add-the-syncfusion-licence-key-to-azure-key-vault">
      Step 1: Add the Syncfusion licence key to Azure Key Vault
    </h3>

  <p>
    In azure Portal Select
  </p>

  <ul>
    <li><strong>Key Vault → Objects  → Secrets → Generate/Import</strong></li><li><b>Name</b>: <em>Syncfusion--LicenseKey</em> Important naming convention: use double dash(--)</li><li><b>Value</b>: <em>Your Syncfusion License Key</em></li><li>Leave all other options as default</li><li>Click <strong>Create</strong></li>
  </ul>

  <p>
    <strong>Why the --?</strong>
  </p>

  <p>
    ASP.NET Core maps:
  </p>

  <p>
    Syncfusion--LicenseKey
  </p>

  <p>
    to:
  </p>

  <p>
    Syncfusion:LicenseKey
  </p>

  <p>
    This allows<b> clean strongly-typed access later.</b>
  </p>

    <h3 id="step-2-ensure-key-vault-is-already-wired-up-youve-done-this">
      Step 2: Ensure Key Vault is already wired up (you’ve done this)
    </h3>

  <p>
    We should already have this in Program.cs:
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new Default
</code></pre>

  <p>
    No more needed here. This means that:
  </p>

  <ul>
    <li>Local → az login</li><li>Azure → Managed Identity</li>
  </ul>

    <h3 id="step-3-remove-the-licence-key-from-appsettingsjson">
      Step 3: Remove the licence key from appsettings.json
    </h3>

  <p>
    If present, remove this line from appsettings.json
  </p>
<pre class="line-numbers  language-json"><code>"SyncfusionLicenceKey": "Your Syncfusion License Key",</code></pre>

  <p>
    After removal, appsettings.json should not contain the licence key at all.
  </p>

    <h3 id="step-4-register-the-syncfusion-licence-at-startup">
      Step 4: Register the Syncfusion licence at startup
    </h3>

  <p>
    Syncfusion requires the licence to be registered before components are used.
  </p>

  <p>
    Open Program.cs
  </p>

  <ul>
    <li>Near the top enter&nbsp;</li><ul><li><code>using Syncfusion.Licensing;</code></li></ul>
  </ul>

  <p>
    After builder.Configuration is available (before builder.Build()), but crucially also after
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());</code></pre>

  <p>
    Enter the follwoing:
  </p>
<pre class="line-numbers  language-csharp"><code>var syncfusionLicenseKey = builder.Configuration["Syncfusion:LicenseKey"];

if (!string.IsNullOrWhiteSpace(syncfusionLicenseKey))
{
    SyncfusionLicenseProvider.RegisterLicense(syncfusionLicenseKey);
}
else
{
    throw new InvalidOperationException("Syncfusion license key not found.");
}
</code></pre>

  <p>
    This will now read straight from Azure Key Vault.
  </p>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="3">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/9/gallery/AzureKeyVault-01-Syncfusion-01.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/9/gallery/AzureKeyVault-01-Syncfusion-01-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/9/gallery/AzureKeyVault-01-Syncfusion-02.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/9/gallery/AzureKeyVault-01-Syncfusion-02-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/9/gallery/AzureKeyVault-03-Syncfusion-02.png" data-size="1035x682">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/9/gallery/AzureKeyVault-03-Syncfusion-02-thumbnail.png" height="506" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h3 id="step-5-test-locally">
      Step 5: Test locally
    </h3>

  <ul>
    <li>Make sure you’re logged in: az login</li><li>Ensure your Entra ID user has:</li><ul><li>Key Vault Secrets User role (or equivalent)</li></ul><li>Run the app</li>
  </ul>

  <p>
    If the licence key is valid:
  </p>

  <ul>
    <li>No Syncfusion watermark</li><li>No licence errors</li>
  </ul>

  <p>
    If something is wrong:
  </p>

  <ul>
    <li>You’ll get the explicit exception we added</li>
  </ul>

    <h3 id="step-6-deploy-to-azure">
      Step 6: Deploy to Azure
    </h3>

  <p>
    When deployed:
  </p>

  <ul>
    <li>DefaultAzureCredential switches to <b>Managed Identity</b></li><li>Azure App Service / Function accesses Key Vault</li><li>Same code, no changes</li><li>No secrets in config</li>
  </ul>

    <h3 id="final-state-clean-secure">
      Final state (clean + secure)
    </h3>
<div><table>
    <tr>
        <td><b>Item</b></td>
        <td><b>Location</b></td>
    </tr>
    <tr>
        <td>Syncfusion licence</td>
        <td>Azure Key Vault</td>
    </tr>
    <tr>
        <td>appsettings.json</td>
        <td>No secrets</td>
    </tr>
    <tr>
        <td>Local auth</td>
        <td>az login</td>
    </tr>
    <tr>
        <td>Azure auth</td>
        <td>Managed Identity</td>
    </tr>
    <tr>
        <td>Code changes</td>
        <td>Minimal</td>
    </tr>
</table></div>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Adding Entra ID credentials to Azure Key Vault</title>
        <author>
            <name>Christopher J Bell</name>
        </author>
        <link href="https://blazorcode.uk/azure/adding-entra-id-credentials-to-azure-key-vault/"/>
        <id>https://blazorcode.uk/azure/adding-entra-id-credentials-to-azure-key-vault/</id>

        <updated>2026-04-16T16:55:17+01:00</updated>
            <summary type="html">
                <![CDATA[
                    Adding Entra ID credentials to Azure Key Vault I asked Microsoft Copilot to help me move some Azure Entra ID configuration from appsettings.json to Azure&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                
    <h2 id="adding-entra-id-credentials-to-azure-key-vault">
      Adding Entra ID credentials to Azure Key Vault
    </h2>

  <p>
    I asked Microsoft Copilot to help me move some Azure Entra ID configuration from appsettings.json to Azure Key Vault. My exact question was:
  </p>

  <p>
    <em>In appsettings.json I have the following (with some values removed)</em>
  </p>
<pre class="line-numbers  language-html"><code>"AzureAd": {
   "Instance": "https://WebCashFlow.ciamlogin.com/",
   "Domain": "WebCashFlow.onmicrosoft.com",
   "TenantId": "tenantID",
   "ClientId": "clientID",
   "ClientSecret": "clientSecret",
   "CallbackPath": "/signin-oidc",
   "SignedOutCallbackPath": "/signout-callback-oidc",
   "SignedOutRedirectUri": "/",
   "RemoteSignOutPath": "/signout-oidc" // optional but good to include
 },
</code></pre>

  <p>
    <em>Firstly, which of the above can I safely keep in appsettings.json?</em><br>
<br><em>Secondly, can you give me a step by step guide to moving the other items to Azure Key Vault.  (I use Visual Studio Insiders, if any part can be done from within VS.)?</em>
  </p>
<hr class="separator separator--dots" />

  <p>
    This is answer I received. and the process I followed.
  </p>

    <h3 id="part-1-what-can-safely-stay-in-appsettingsjson">
      Part 1 – What can safely stay in appsettings.json?
    </h3>

  <p>
    Here is your configuration again, classified line by line:
<br><code>"AzureAd": {
<br>  "Instance": "https://WebCashFlow.ciamlogin.com/",
<br>  "Domain": "WebCashFlow.onmicrosoft.com",
<br>  "TenantId": "tenantID",
<br>  "ClientId": "clientID",
<br>  "ClientSecret": "clientSecret",
<br>  "CallbackPath": "/signin-oidc",
<br>  "SignedOutCallbackPath": "/signout-callback-oidc",
<br>  "SignedOutRedirectUri": "/",
<br>  "RemoteSignOutPath": "/signout-oidc"
<br>}&nbsp;</code>
  </p>

    <h5 id="safe-to-keep-in-appsettingsjson">
      SAFE to keep in appsettings.json:
    </h5>

  <p>
    These are identifiers or URLs, not secrets:
  </p>

  <ul>
    <li>Instance</li><li>Domain</li><li>TenantId</li><li>ClientId</li><li>CallbackPath</li><li>SignedOutCallbackPath</li><li>SignedOutRedirectUri</li><li>RemoteSignOutPath&nbsp;</li>
  </ul>

  <p>
    These values are routinely visible in front end requests and metadata endpoints.&nbsp;<strong>Keep them exactly where they are</strong>.
  </p>

  <p>
    <strong>MUST be moved to Azure Key Vault</strong>
  </p>

  <ul>
    <li>ClientSecret</li>
  </ul>

  <p>
    Why?
  </p>

  <ul>
    <li>It is a credential</li><li>Anyone who gets this can impersonate your app against Entra ID</li><li>Treat it with the same care as a database password or Stripe secret key</li>
  </ul>

    <h3 id="part-2-target-end-state">
      Part 2 - Target end state
    </h3>

  <p>
    &nbsp;At the end of this step:
  </p>
<div><table>
    <tr>
        <td><b>Location</b></td>
        <td><b>Contains</b></td>
    </tr>
    <tr>
        <td>appsettings.json</td>
        <td>Non‑sensitive AzureAd settings</td>
    </tr>
    <tr>
        <td>Azure Key Vault</td>
        <td>AzureAd--ClientSecret</td>
    </tr>
    <tr>
        <td>Code</td>
        <td>No changes to authentication code</td>
    </tr>
</table>
</div>

  <p>
    Your app will still read Configuration["AzureAd:ClientSecret"], but <strong>Key Vault will supply it instead of a file.</strong>
  </p>

    <h3 id="part-3-step-by-step-move-clientsecret-to-azure-key-vault">
      Part 3 - Step by step: Move ClientSecret to Azure Key Vault
    </h3>

    <h5 id="step-1-create-an-azure-key-vault">
      Step 1 – Create an Azure Key Vault
    </h5>

    <h6 id="andnbspazure-portal-recommended-for-first-time">
      &nbsp;Azure Portal (recommended for first time)
    </h6>

  <p>
    Before getting into the detail, I have a question. Having logged into the Azure portal, the first question is 'Which Directory'. These are the directories I appear to have:
  </p>
<div><table>
    <tr>
        <td><b>Directory Name</b></td>
        <td><b>Domain</b></td>
        <td><b>DirectoryID</b></td>
    </tr>
    <tr>
        <td>Christopher Bell</td>
        <td>blazorcode.uk</td>
        <td><i>adfa128e-9813-4c50-819e-27d98e405467</i></td>
    </tr>
    <tr>
        <td>BlazorCodeEntraTenant</td>
        <td>BlazorcodeEntra.onmicrosoft.com</td>
        <td><i>3806082c-cff7-4294-8c49-128e4f7bf757</i></td>
    </tr>
    <tr>
        <td>CashflowProject</td>
        <td>TestCashFlow.onmicrosoft.com</td>
        <td><i>f9cb96f8-ae08-4e4a-85b0-e339bbd5b365</i></td>
    </tr>
    <tr>
        <td>CashFlowV1B2C</td>
        <td>CashFlowV1B2C.onmicrosoft.com</td>
        <td><i>caf2a0db-4964-4329-8292-668e8249185c</i></td>
    </tr>
    <tr>
        <td>Default Directory</td>
        <td>christopherbelloutlook.onmicrosoft.com</td>
        <td><i>ddc7d405-42f5-414d-814c-c86c4bbc800d</i></td>
    </tr>
    <tr>
        <td>WebCashFlow</td>
        <td>WebCashFlow.onmicrosoft.com</td>
        <td><i>bfd1bc16-4cc8-420d-9e74-fcde29dbfb32</i></td>
    </tr>
</table>
</div>

  <p>
    It is not clear to me which directory I should use. The ones I can (probably) discount are:
  </p>
<div><table>
    <tr>
        <td><b>Directory Name</b></td>
        <td><b>Reason</b></td>
    </tr>
    <tr>
        <td>Christopher Bell</td>
        <td>Possible? Default directory</td>

    </tr>
    <tr>
        <td>BlazorCodeEntraTenant</td>
        <td>Possible? Entra ID directory for WebCashFlow</td>

    </tr>
    <tr>
        <td>CashflowProject</td>
        <td>Probably not. Original for Charles Sanderson?</td>

    </tr>
    <tr>
        <td>CashFlowV1B2C</td>
        <td>Probably not. Looks like Azure B2C for original CashFlow</td>

    </tr>
    <tr>
        <td>Default Directory</td>
        <td>Definitely not. Old personal account directory</td>

    </tr>
    <tr>
        <td>WebCashFlow</td>
        <td>Possible?</td>
  
    </tr>
</table></div>

  <ul>
    <li>By switching to 'WebCashFlow' I can see that this directory contains the Entra ID users for my WebCashFlow project, so this could be possible.&nbsp; However, when I tried to 'create a resource' in this directory I got a message that stated there was no Azure subscription for this directory - so could not create any resource.</li>
  </ul>

  <ul>
    <li>I faced the same problem after switching to 'BlazorCodeEntraTenant'. This looks to be another directory used for Entra ID testing, but had fewer users than WebCashFlow, so probably isn't the 'live' Entra ID directory for WebCashFlow users.</li>
  </ul>

  <ul>
    <li>Switching directory to 'CashFlowProject' required 2FA which I was unable to provide, so not that directory.</li>
  </ul>

  <ul>
    <li>Switching directory to 'CashFlowV1B2C' also required 2FA which I was unable to provide, so not that directory.</li>
  </ul>

  <ul>
    <li>It definitely isn't going to be the directory associated with my personal Outlook account, so by a matter of elimination, it must be the directory associated with 'blazorcode.uk'</li>
  </ul>

  <p>
    I therefore assume I should use the 'blazorcode.uk' directory.
  </p>

  <ul>
    <li>Go to Azure Portal → Create a resource</li><li>Search for Key Vault</li><li>Create</li><ul><li><span style="font-size: 1em;">Select Subscription</span></li><li><span style="font-size: 1em;">Select Resource Group (or add a new resource group)</span></li><li><span style="font-size: 1em;">Name: wcf-keyvault (This is the name I ended up with, the screenshot shows my first attempt, with a different name.)</span></li><li><span style="font-size: 1em;">Region: UK South</span></li><li><span style="font-size: 1em;">Pricing tier: Standard</span></li></ul><li><span style="font-size: 1em;">Leave networking as default for now</span></li><li><span style="font-size: 1em;">Create the vault</span></li>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="4">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-01.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-01-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-02.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-02-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-03.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-03-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-04.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-04-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h5 id="step-2-andnbspadd-the-client-secret-to-key-vault">
      Step 2 –&nbsp;Add the Client Secret to Key Vault
    </h5>

  <p>
    I got as far as creating the Azure Key Vault, however when I tried to add a Secret I got the following error message “<em>The operation is not allowed by RBAC. If role assignments were recently changed, please wait several minutes for role assignments to become effective. </em>“
  </p>

  <p>
    The answer to this problem was to give myself permission to add secrets.
  </p>

  <ul>
    <li>In the Key Vault:</li><ul><li>Select <b>Access Control (IAM)</b></li><li><font color="#02192b">Select <b>+ Add</b></font></li><li><font color="#02192b">In the <b>Roles</b> tab</font></li><ul><li><font color="#02192b">Enter <i>Key Vault Secrets Officer</i> in the Search box</font></li><li><font color="#02192b">A list should be displayed; highlight <i>Key Vaults Secrets Officer </i>and click <b>Next</b></font></li></ul><li><span style="color: rgb(2, 25, 43); font-size: 1em;">On the </span><b style="color: rgb(2, 25, 43); font-size: 1em;">Members</b><span style="color: rgb(2, 25, 43); font-size: 1em;"> tab</span></li><ul><li><font color="#02192b">Select <b>+ Select Members</b></font></li><li><font color="#02192b">In the right-hand pane, highlight the member and click <b>Select</b></font></li><li><font color="#02192b">The right-hand pane will close and the member will be displayed in the main pane. Select <b>Review + assign</b></font></li></ul></ul>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="5">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-08.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-08-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-09.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-09-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-10-2.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-10-2-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-11-2.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-11-2-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-12-2.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-12-2-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h6 id="add-the-client-secret-to-key-vault">
      Add the Client Secret to Key Vault
    </h6>

  <ul>
    <li>In the Key Vault:</li><ul><li>Select <b>Objects</b></li><li>Select <b>Secrets</b></li><li>Click <b>+ Generate/Import</b></li><li>Set</li><ul><li><b>Name:</b> AzureAd--ClientSecret</li><li><b>Value:</b> your <b>actual</b> Entra ID client secret</li></ul><li>Create</li></ul>
  </ul>

  <p>
    Note the <b>double dashes (--)</b>. This maps automatically to AzureAd:ClientSecret.
  </p>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="3">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-06.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-06-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-07png.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-07png-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-14.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-14-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h5 id="step-3-allow-your-app-developer-to-read-the-secret">
      Step 3 – Allow your app (developer) to read the secret
    </h5>

  <p>
    For now we’ll use your own Azure identity so you can test locally.
  </p>

  <ul>
    <li>In Key Vault → <b>Access control (IAM)</b></li><ul><li>Add role assignment:</li><ul><li><b>Role:</b> Key Vault Secrets User</li><li><b>Assign access to:</b> User</li><li><b>Member:</b> your Azure login</li></ul></ul>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="4">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-15.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-15-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-16.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-16-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-17.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-17-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-18.png" data-size="1592x935">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-18-thumbnail.png" height="451" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h5 id="step-4-install-required-nuget-packages">
      Step 4 – Install required NuGet packages
    </h5>

  <p>
    In Visual Studio, add NuGet packages:
  </p>

  <ul>
    <li>Azure.Identity</li><li>Azure.Extensions.AspNetCore.Configuration.Secrets</li>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="1">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-19.png" data-size="1600x1018">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-19-thumbnail.png" height="489" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h5 id="step-5-wire-key-vault-into-programcs">
      Step 5 – Wire Key Vault into Program.cs
    </h5>

  <p>
    Open Program.cs
  </p>

  <p>
    At the top of the file insert:
  </p>
<pre class="line-numbers  language-csharp"><code>using Azure.Identity;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
</code></pre>

  <p>
    Add, after <em>var builder = WebApplication.CreateBuilder(args)</em>
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());</code></pre>

  <p>
    Note the Uri of the key vault name.
  </p>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="1">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-20.png" data-size="795x518">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/8/gallery/AzureKeyVault-20-thumbnail.png" height="500" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

  <p>
    In theory, this does not break anything.
  </p>

  <ul>
    <li>If Key Vault is unreachable → app still loads</li><li>If secret is found → it overrides file values</li>
  </ul>

    <h5 id="step-6-remove-clientsecret-from-appsettingsjson">
      Step 6 – Remove ClientSecret from appsettings.json
    </h5>

  <p>
    Edit appsettings.json:
  </p>
<pre class="line-numbers  language-csharp"><code>"AzureAd": {
  "Instance": "https://WebCashFlow.ciamlogin.com/",
  "Domain": "WebCashFlow.onmicrosoft.com",
  "TenantId": "tenantID",
  "ClientId": "clientID",
  "CallbackPath": "/signin-oidc",
  "SignedOutCallbackPath": "/signout-callback-oidc",
  "SignedOutRedirectUri": "/",
  "RemoteSignOutPath": "/signout-oidc"
}
</code></pre>

  <ul>
    <li>No code changes required elsewhere</li><li>Configuration["AzureAd:ClientSecret"] still resolves</li>
  </ul>

    <h5 id="step-7-test-locally">
      Step 7 – Test locally
    </h5>

  <ul>
    <li>Sign in to Azure CLI: <strong>az login</strong> - <b>What's this?</b> See next section</li><li>Run your Blazor Server app</li><li>Attempt authentication</li>
  </ul>

  <p>
    Expected result:
  </p>

  <ul>
    <li>Sign in works exactly as before</li><li>No secrets on disk</li><li>You can rotate the secret without redeploying</li>
  </ul>

    <h4 id="azure-cli">
      Azure CLI
    </h4>

  <p>
    These are the instructions for running the Azure CLI. Two commands have been suggested
  </p>

  <ul>
    <li>az login</li><li>az account show</li>
  </ul>

    <h5 id="option-1-recommended-azure-cloud-shell-in-the-azure-portal">
      Option 1 (Recommended): Azure Cloud Shell in the Azure Portal
    </h5>

  <p>
    This is the easiest and safest option — nothing to install. (But I'm not sure it would work for the 'Sign into Azure CLI: az login' in the Test locally scenario above.)
  </p>

  <ul>
    <li>Steps</li><ul><li>Go to https://portal.azure.com</li><li>Log in</li><li>In the top right toolbar, click the Cloud Shell icon "&gt;_"</li><li>When prompted:</li><ul><li>Choose Bash</li><li>Create storage if asked (you can accept defaults)</li></ul><li>Run the command: <em>az account show</em></li></ul>
  </ul>

    <h5 id="option-2-local-machine-actually-this-is-needed-to-test-locally">
      Option 2: Local machine (actually, this is needed to Test Locally)
    </h5>

  <p>
    If you prefer running commands locally.
  </p>

    <h6 id="step-1-install-azure-cli-if-not-already-installed">
      Step 1 – Install Azure CLI (if not already installed)
    </h6>

  <ul>
    <li>Windows:</li><li><a href="https://learn.microsoft.com/cli/azure/install-azure-cli-windows" target="_blank">https://learn.microsoft.com/cli/azure/install-azure-cli-windows</a>&nbsp;<br></li>
  </ul>

    <h6 id="step-2-open-a-terminal">
      Step 2 – Open a terminal
    </h6>

  <ul>
    <li>Windows</li><li>PowerShell or Command Prompt</li>
  </ul>

    <h6 id="step-3-log-in">
      Step 3 – Log in
    </h6>

  <ul>
    <li><em>az login</em></li><li>This opens a browser window. Sign in with your Azure account.</li>
  </ul>

    <h6 id="step-4-run-the-command">
      Step 4 – Run the command
    </h6>

  <ul>
    <li>az account show</li>
  </ul>

    <h6 id="what-output-you-should-look-for">
      What output you should look for
    </h6>

  <p>
    The command returns JSON like this:
  </p>
<pre class="line-numbers  language-json"><code>{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "name": "Pay-As-You-Go",
  "tenantId": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
  "user": {
    "name": "you@yourdomain.com",
    "type": "user"
  }
}
</code></pre>

  <p>
    The important fields:
  </p>
<div><table>
    <tr>
        <td><b>Field</b></td>
        <td><b>Why it matters</b></td>
    </tr>
    <tr>
        <td>tenantId</td>
        <td>This is the directory you are currently operating in</td>
    </tr>
    <tr>
        <td>user.name</td>
        <td>Confirms who you’re logged in as</td>
    </tr>
    <tr>
        <td>id</td>
        <td>Subscription being used</td>
    </tr>
</table></div>

    <h5 id="why-run-az-login-to-test-the-application-locally">
      Why run az login to test the application locally?
    </h5>

    <h6 id="short-answerandnbsp">
      Short Answer:&nbsp;
    </h6>

  <p>
    You need to run <em>az login</em> locally because DefaultAzureCredential needs some identity to authenticate as, and on your local machine that identity usually comes from the Azure CLI login.
  </p>

    <h6 id="long-answer">
      Long Answer:
    </h6>

  <p>
    What DefaultAzureCredential actually does:
  </p>

  <p>
    DefaultAzureCredential <b>does not magically authenticate by itself</b>.
<br>Instead, it tries a <b>chain of possible credential sources</b> in a fixed order and uses the <b>first one that works.</b>
  </p>

  <p>
    Simplified, the order looks like this:
  </p>

  <ol>
    <li><b>Environment variables</b> (e.g. AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)</li><li><b>Managed Identity</b> (only works inside Azure: App Service, VM, Function, etc.)</li><li><b>Visual Studio / Visual Studio Code login</b></li><li><b>Azure CLI (az login)</b></li><li><b>Azure PowerShell (Connect-AzAccount)</b></li>
  </ol>

  <p>
    On a <b>local development machine</b>, steps 1 and 2 usually <b>do not apply</b>, so it eventually falls through to <b>Azure CLI authentication</b>.
  </p>

  <p>
    This is why az login matters.
  </p>

  <p>
    Why az login is required locally?<br>When you run:
  </p>
<pre class="line-numbers  language-csharp"><code>builder.Configuration.AddAzureKeyVault(
    new Uri("https://wcf-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());
</code></pre>

  <p>
    &nbsp;Your app asks:&nbsp;“Who am I, and what identity should I present to Key Vault?”<br>
  </p>

  <p>
    On your local machine:
  </p>

  <ul>
    <li>There is no <b>Managed Identity</b></li><li>There may be no <b>environment variables</b></li><li>But there can be an <b>Azure CLI login</b></li>
  </ul>

  <p>
    When you run
  </p>

  <p>
    az login
  </p>

  <p>
    You are:
  </p>

  <ul>
    <li>Authenticating your <b>Entra ID user account</b></li><li>Storing a token securely in the Azure CLI cache</li><li>Making yourself available as an identity provider</li>
  </ul>

  <p>
    DefaultAzureCredential then:
  </p>

  <ul>
    <li>Detects the Azure CLI token</li><li>Uses <b>your signed in Entra ID user</b> to request access tokens</li><li>Calls Key Vault <b>as you</b></li>
  </ul>
<hr class="separator separator--dots" />

  <p>
    <strong>What identity is being used after az login?</strong>
  </p>

  <p>
    <b>Your own Entra ID user account</b>, not the app’s client secret.&nbsp;This is important.
  </p>

  <ul>
    <li>That means:</li><ul><li>Your user must have at least:</li><ul><li><b>Key Vault Secrets User</b>, or</li><li><b>Key Vault Secrets Officer</b>, or</li><li>an equivalent access policy / RBAC role</li></ul><li>This access is <b>separate from your app’s production identity</b></li></ul>
  </ul>

  <p>
    This is by design and is considered best practice.
  </p>

  <p>
    <b>Why this is good practice:</b>
  </p>

  <ul>
    <li><b>Security</b>:</li><ul><li>You do not store client secrets locally</li><li>No secrets in appsettings.json</li><li>No secrets checked in to source control</li></ul><li><span style="font-size: 1em;"><strong>Environment separation</strong></span></li><ul><li>•	Local → uses <b>developer identity</b></li><li>•	Azure → uses <b>Managed Identity</b></li><li>•	No code changes required between environments</li></ul><li><span style="font-size: 1em;"><b>Same code everywhere</b></span></li>
  </ul>

  <p>
    Your code never changes; only where the identity comes from does.
  </p>

  <p>
    <b>What happens in Azure (production)?</b>
  </p>

  <p>
    When you deploy to Azure (App Service, Function App, etc.):
  </p>

  <ul>
    <li>DefaultAzureCredential stops at: ✅ <b>Managed Identity</b></li><li>az login is <b>not used</b></li><li>Your Entra ID application <b>does not need the ClientSecret</b></li><li>Key Vault access is granted to the <b>Managed Identity</b></li>
  </ul>

  <p>
    So this: new DefaultAzure
<br>✅ Works locally
<br>✅ Works in Azure
<br>✅ No conditionals
<br>✅ No secrets in config
  </p>

  <p>
    <strong>Common misunderstanding about the ClientSecret</strong>
  </p>

  <p>
    You mentioned:&nbsp;“I added a secret for my Azure Entra ID ‘ClientSecret'"
  </p>

  <p>
    That secret <b>is not being used locally</b> if you are relying on az login.
  </p>

  <p>
    It would only be used if:
  </p>

  <ul>
    <li>You explicitly configured ClientSecretCredential, or</li><li>You set:</li><ul><li>AZURE_CLIENT_ID</li><li>AZURE_TENANT_ID&nbsp;</li></ul>
  </ul>

  <p>
    &nbsp;Most modern ASP.NET guidance recommends <b>not using client secrets at all</b> if Managed Identity is available.
  </p>

  <p>
    <b>Summary</b>
  </p>

  <p>
    <b>Why do I need az login locally?</b>
  </p>

  <ul>
    <li>Because:</li><ul><li>DefaultAzureCredential needs an identity</li><li>Locally, the simplest identity source is the Azure CLI</li><li>az login provides that identity securely</li><li>Your user account is used to access Key Vault</li><li>This mirrors how Managed Identity works in Azure</li></ul>
  </ul>

    <h4 id="my-conclusion">
      My Conclusion
    </h4>

  <p>
    It's complicated - but it appears to work!
  </p>
            ]]>
        </content>
    </entry>
    <entry>
        <title>Changing Users&#x27; Rights</title>
        <author>
            <name>Christopher J Bell</name>
        </author>
        <link href="https://blazorcode.uk/azure/what-and-how/"/>
        <id>https://blazorcode.uk/azure/what-and-how/</id>

        <updated>2025-11-08T10:05:27+00:00</updated>
            <summary type="html">
                <![CDATA[
                    Making a User an Admin This process assumes that the 'Admin' role has already been created. Log into the Azure PortalChange directory to the one&hellip;
                ]]>
            </summary>
        <content type="html">
            <![CDATA[
                
    <h2 id="making-a-user-an-admin">
      Making a User an Admin
    </h2>

  <p>
    This process assumes that the 'Admin' role has already been created.
  </p>

  <ul>
    <li>Log into the Azure Portal</li><li>Change directory to the one containing the Entra External ID</li><li>Select 'Microsoft Entra ID' from Azure Services</li><ul><li>Or enter 'Entra' in the search bar</li></ul><li>Select 'Enterprise applications'</li><li>Select the application from the list displayed in the body of the screen</li><li>Select 'Manage &gt; Users &amp; Groups' from the menu</li><li>Click 'Add user/group' (No need to select user at this stage)</li><li>Click 'None Selected' (in this case)</li><li>This will open a pane to the right. Tick the users you want to assign to the role. Click 'Select' at the foot of the pane.</li><li>In my case only 'Admin' was available as a role and was selected automatically.</li><li>Click 'Assign'</li>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="3">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/20-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/20-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/21-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/21-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/23-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/23-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/24-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/24-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/25-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/25-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/26-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/26-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/27-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/27-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/28-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/28-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/29-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/29-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>

    <h2 id="to-remove-admin-rights-from-a-user">
      To remove Admin rights from a User
    </h2>

  <ul>
    <li>Log into the Azure Portal
</li><li>Change directory to the one containing the Entra External ID
</li><li>Select 'Microsoft Entra ID' from Azure Services
</li><li>Or enter 'Entra' in the search bar
</li><li>Select 'Enterprise applications'
</li><li>Select the application from the list displayed in the body of the screen
</li><li>Select 'Manage &gt; Users &amp; Groups' from the menu</li><li>Tick the user you wish to remove Admin rights from - make sure you select the user with Admin 'Role assigned'.</li><li>Click 'Remove assignment' on the toolbar.</li><li><br></li>
  </ul>

  <div  class="gallery-wrapper">
    <div class="gallery" data-columns="2">
      <figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/31-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/31-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure><figure class="gallery__item">
      <a href="https://blazorcode.uk/azure/media/posts/1/gallery/32-Azure-Roles.png" data-size="1600x1066">
        <img loading="lazy" src="https://blazorcode.uk/azure/media/posts/1/gallery/32-Azure-Roles-thumbnail.png" height="512" width="768" alt="" >
      </a>
      
    </figure>
    </div>
  </div>
            ]]>
        </content>
    </entry>
</feed>
