<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://hals.app/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hals.app/" rel="alternate" type="text/html" /><updated>2026-06-19T14:58:28+00:00</updated><id>https://hals.app/feed.xml</id><title type="html">hals.app</title><subtitle>Some thoughts while not coding stuff.</subtitle><author><name>Nils Henrik Hals</name></author><entry><title type="html">C# Union Types: Discriminated Unions in C# 15!</title><link href="https://hals.app/blog/csharp-union-types/" rel="alternate" type="text/html" title="C# Union Types: Discriminated Unions in C# 15!" /><published>2026-06-19T00:00:00+00:00</published><updated>2026-06-19T00:00:00+00:00</updated><id>https://hals.app/blog/csharp-union-types</id><content type="html" xml:base="https://hals.app/blog/csharp-union-types/"><![CDATA[<p>As I have previously blogged about in <a href="/blog/record-distinct-types/">Records as distinct types</a>, C# has been evolving rapidly in terms of type safety and expressiveness. C# 15 and dotnet 11 finally introduces real <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union">Union Types</a>, which allow developers to define a type that can represent multiple different types!</p>

<p>Not sure why this is important? Keep on reading!</p>

<h2 id="what-are-union-types">What are Union Types?</h2>

<p>Union Types, also known as Discriminated Unions or Sum Types, are a way to define a type that can hold one of several different types. This is particularly useful when you want to represent a value that can be one of several different forms. For example, you might have a <code class="language-plaintext highlighter-rouge">Result</code> type that can either be a <code class="language-plaintext highlighter-rouge">Success</code> with a value or a <code class="language-plaintext highlighter-rouge">Error</code> with an error message.</p>

<p>This allows you to write code that is more expressive and easier to understand, as you can clearly indicate the different possible states of a value.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Success</span><span class="p">(</span><span class="kt">string</span> <span class="n">Result</span><span class="p">);</span>
<span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Error</span><span class="p">(</span><span class="kt">string</span> <span class="n">Message</span><span class="p">);</span>

<span class="k">public</span> <span class="n">union</span> <span class="nf">Result</span><span class="p">(</span><span class="n">Success</span><span class="p">,</span> <span class="n">Error</span><span class="p">);</span>
</code></pre></div></div>

<p>We can then use pattern matching on the various possible instance of the “RequestState” (and we HAVE to do this!).</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Result</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">GetDataFromApi</span><span class="p">();</span>

<span class="kt">var</span> <span class="n">textToConsole</span> <span class="p">=</span> <span class="n">result</span> <span class="k">switch</span>
<span class="p">{</span>
    <span class="n">Success</span> <span class="n">success</span> <span class="p">=&gt;</span> <span class="s">$"Success: </span><span class="p">{</span><span class="n">success</span><span class="p">.</span><span class="n">Result</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
    <span class="n">Error</span> <span class="n">error</span> <span class="p">=&gt;</span> <span class="s">$"Error: </span><span class="p">{</span><span class="n">error</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
<span class="p">};</span>

<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">textToConsole</span><span class="p">);</span>
</code></pre></div></div>

<p>This allows the API design to surface the valid error states for example:</p>

<p>We could further specify the expected error states with a nested union type:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Success</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="n">Value</span><span class="p">);</span>

<span class="k">public</span> <span class="n">union</span> <span class="nf">Error</span><span class="p">(</span><span class="n">FileNotFoundException</span><span class="p">,</span> <span class="n">OutOfMemoryException</span><span class="p">);</span>

<span class="k">public</span> <span class="n">union</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">Success</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;,</span> <span class="n">Error</span><span class="p">);</span>
</code></pre></div></div>

<p>This way we can be even more specific about the possible error states and handle them accordingly.</p>

<p>We can then surface that a method may throw an exception we should handle:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Fizz</span><span class="p">;</span>
<span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Buzz</span><span class="p">;</span>
<span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">FizzBuzz</span><span class="p">;</span>
<span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">None</span><span class="p">;</span>
<span class="k">public</span> <span class="n">union</span> <span class="nf">FizzBuzzResult</span><span class="p">(</span><span class="n">Fizz</span><span class="p">,</span> <span class="n">Buzz</span><span class="p">,</span> <span class="n">FizzBuzz</span><span class="p">,</span> <span class="n">None</span><span class="p">,</span> <span class="n">ArgumentOutOfRangeException</span><span class="p">);</span>

<span class="k">public</span> <span class="k">static</span> <span class="n">FizzBuzzResult</span> <span class="nf">FizzBuzzForNumber</span><span class="p">(</span><span class="kt">int</span> <span class="n">number</span><span class="p">)</span>
<span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="p">&lt;</span> <span class="m">0</span> <span class="p">||</span> <span class="n">number</span> <span class="p">&gt;</span> <span class="m">100</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">ArgumentOutOfRangeException</span><span class="p">(</span><span class="k">nameof</span><span class="p">(</span><span class="n">number</span><span class="p">),</span> <span class="s">"number must be between 0 and 100"</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="p">%</span> <span class="m">15</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">FizzBuzz</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="p">%</span> <span class="m">3</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">Fizz</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="p">%</span> <span class="m">5</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">Buzz</span><span class="p">();</span>

  <span class="k">return</span> <span class="k">new</span> <span class="nf">None</span><span class="p">();</span>
<span class="p">}</span>

<span class="n">FizzBuzzResult</span> <span class="n">result</span> <span class="p">=</span> <span class="nf">FizzBuzzForNumber</span><span class="p">(</span><span class="m">15</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">textToConsole</span> <span class="p">=</span> <span class="n">result</span> <span class="k">switch</span>
<span class="p">{</span>
  <span class="n">Fizz</span> <span class="p">=&gt;</span> <span class="s">"Fizz"</span><span class="p">,</span>
  <span class="n">Buzz</span> <span class="p">=&gt;</span> <span class="s">"Buzz"</span><span class="p">,</span>
  <span class="n">FizzBuzz</span> <span class="p">=&gt;</span> <span class="s">"FizzBuzz"</span><span class="p">,</span>
  <span class="n">None</span> <span class="p">=&gt;</span> <span class="s">"Snooze"</span><span class="p">,</span>
  <span class="n">ArgumentOutOfRangeException</span> <span class="n">ex</span> <span class="p">=&gt;</span> <span class="s">$"Error: </span><span class="p">{</span><span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
<span class="p">};</span>
<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">textToConsole</span><span class="p">);</span>
</code></pre></div></div>

<p>Neat! We can now represent the possible states of our data and also the possible exceptions in a clear and concise way, and we are forced to handle all cases when we use pattern matching. This is a huge win for type safety and code clarity!</p>

<h2 id="could-i-not-just-use-inheritance-and-polymorphism">Could I not just use Inheritance and Polymorphism?</h2>

<p>Before Union Types, we often had to rely on inheritance and polymorphism to achieve similar functionality. However, this approach can lead to complex class hierarchies and can make it difficult to understand the different states of a value. With Union Types, we can represent these states more clearly and concisely without the need for complex class structures.</p>

<p>Example:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Success</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">T</span> <span class="n">Value</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="nf">Success</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">Value</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Error</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="kt">string</span> <span class="n">Message</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
    <span class="k">public</span> <span class="nf">Error</span><span class="p">(</span><span class="kt">string</span> <span class="n">message</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="n">Message</span> <span class="p">=</span> <span class="n">message</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This approach can lead to a more complex class hierarchy and can make it less clear what the different states of a value are. With Union Types, we can represent these states more clearly and concisely without the need for complex class structures.</p>

<p>In the code we would handle this with pattern matching as well, but it is more verbose and less clear:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Result</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="nf">GetDataFromApi</span><span class="p">();</span>
<span class="kt">var</span> <span class="n">textToConsole</span> <span class="p">=</span> <span class="n">result</span> <span class="k">switch</span>
<span class="p">{</span>
  <span class="n">Success</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">success</span> <span class="p">=&gt;</span> <span class="s">$"Success: </span><span class="p">{</span><span class="n">success</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
  <span class="n">Error</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;</span> <span class="n">error</span> <span class="p">=&gt;</span> <span class="s">$"Error: </span><span class="p">{</span><span class="n">error</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">,</span>
  <span class="c1">// The line below is a runtime error instead of a compile time error if we forget to handle a new case... Whoops my app crashed...</span>
  <span class="n">_</span> <span class="p">=&gt;</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">InvalidOperationException</span><span class="p">(</span><span class="s">"Unknown result type"</span><span class="p">),</span>
<span class="p">};</span>

<span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="n">textToConsole</span><span class="p">);</span>
</code></pre></div></div>

<p>Overall, Union Types provide a powerful way to represent values that can be one of several different types, and they bring a new level of flexibility and safety to C# programming. They allow us to write code that is more expressive and easier to understand, and they can help us avoid complex class hierarchies and improve the clarity of our code.</p>

<h2 id="one-more-thing">One more thing!</h2>

<p>Closed classes/records in C#. This is a new type similar to the union type. This lets us use the pattern from above, while keeping a lot of the benefits of the union type.</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">public</span> <span class="n">closed</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;;</span>

<span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Success</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">T</span> <span class="n">Value</span><span class="p">)</span> <span class="p">:</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;;</span>
<span class="k">public</span> <span class="n">record</span> <span class="k">class</span> <span class="nc">Error</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">Message</span><span class="p">)</span> <span class="p">:</span> <span class="n">Result</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;;</span>
</code></pre></div></div>

<p>This allows us to have a base type that can only be inherited by a specific set of types, which can be useful for representing a closed set of possible values. It is NOT the same as sealed. The closed record is always abstract, and the base class can not be inherited in other assemblies.</p>

<p>This allows exhaustive <code class="language-plaintext highlighter-rouge">switch</code> statements and typeguards on normal classes.</p>

<h2 id="final-thoughts">Final thoughts</h2>

<p>I’ve tested this in dotnet <code class="language-plaintext highlighter-rouge">11.0.0.preview-5</code> and I think this is a promising start of Union Types in C#.</p>

<p>I assume and hope that there will be shared libraries for common union types such as the <code class="language-plaintext highlighter-rouge">Result</code> and possibly more general union tuples.</p>

<p>I’d also like more “duck typing” support, where shared fields between the different union types could be used without deconstructing. <code class="language-plaintext highlighter-rouge">union Shape(Square, Circle)</code> -&gt; <code class="language-plaintext highlighter-rouge">shape.Name</code>. It should also be possible to have function parameters in a similar style to TypeScript (for example <code class="language-plaintext highlighter-rouge">function name(animal: Dog | Cat) { return animal.name; }</code>). I’d expect more syntax sugar to come in the future to make this even more ergonomic.</p>

<p>Overall, I think this is a great addition to C# and I’m excited to see how it will be used in the future! Happy coding! 🚀</p>

<h2 id="further-reading">Further reading</h2>

<p>If you want to learn more about Union Types, here are some resources I recommend. I started learning about Union Types from Elm and TypeScript, so I suggest checking out their documentation as well!</p>

<ul>
  <li><a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union">Microsoft docs on Union Types</a></li>
  <li><a href="https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-types">Union Types in TypeScript</a></li>
  <li><a href="https://guide.elm-lang.org/types/union_types.html">Union Types in Elm</a></li>
</ul>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="csharp" /><summary type="html"><![CDATA[As I have previously blogged about in Records as distinct types, C# has been evolving rapidly in terms of type safety and expressiveness. C# 15 and dotnet 11 finally introduces real Union Types, which allow developers to define a type that can represent multiple different types!]]></summary></entry><entry><title type="html">No More Secrets: Federated Credentials in Azure and EntraID</title><link href="https://hals.app/blog/entraid-avoid-clientsecrets-azure/" rel="alternate" type="text/html" title="No More Secrets: Federated Credentials in Azure and EntraID" /><published>2026-06-19T00:00:00+00:00</published><updated>2026-06-19T00:00:00+00:00</updated><id>https://hals.app/blog/entraid-avoid-clientsecrets-azure</id><content type="html" xml:base="https://hals.app/blog/entraid-avoid-clientsecrets-azure/"><![CDATA[<p>This is a guide to help you avoid using client secrets in Azure when authenticating with EntraID (Azure AD) and Microsoft Identity Web.</p>

<h2 id="intro">Intro</h2>

<p>A common pattern when hosting an application in Azure using EntraID is to have an AppRegistration containing the application’s authorization setup. The app usually connects to some downstream APIs and this requires the host service to have an identification. This is often done with a ClientId and a ClientSecret combination.</p>

<p>The ClientSecret is a generated string, and should be both rotated frequently and also creates a risk of exposure in appsettings and env variables.</p>

<p>There exists a way to avoid ClientSecrets entirely, using Federated Credentials!</p>

<h2 id="what-are-federated-credentials">What are federated credentials?</h2>

<p>Federated credentials is a way to add permissions directly to a specified “Identity”. So instead of handing out a secret “key” we ask the identity to identify itself and then give the correct permissions. This helps us completely avoid any secrets, expiration etc!</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>This guide assumes you have an:</p>

<ul>
  <li>ASP.NET API service setup with the <code class="language-plaintext highlighter-rouge">Microsoft.Identity.Web</code> package for Authentication and Authorization.</li>
  <li>App hosted in Azure, the guide assumes using an AppService but other hosting options should work as well.</li>
  <li>App Registration in Azure AD for your API</li>
</ul>

<h2 id="setting-up-federated-credentials">Setting up federated credentials</h2>

<ol>
  <li>Create a User Assigned Managed Identity in Azure. This will be the identity of your app when it connects to downstream APIs.
    <ol>
      <li>Go to the Azure Portal, navigate to “Managed Identities” and create a new User Assigned Managed Identity.
        <ol>
          <li>I suggest having a naming convention for your identities, for example: <code class="language-plaintext highlighter-rouge">id-[app-name]</code> to easily identify them later.</li>
          <li>Place them in the same region as your app.</li>
        </ol>
      </li>
      <li>Note down the ClientId of the Managed Identity, you will need it later.</li>
    </ol>
  </li>
  <li>Go to your App Registration in Azure AD and navigate to “Certificates &amp; secrets” -&gt; “Federated credentials” and add a new credential.
    <ol>
      <li>Select “User Assigned Managed Identity” as the credential type.</li>
      <li>Select the subscription and the Managed Identity you created in step 1.</li>
      <li>For the “Subject identifier” field, use the ClientId of the Managed Identity (from step 1.2).</li>
      <li>The Name field is just a friendly name for you to identify the credential. <code class="language-plaintext highlighter-rouge">cred-id-[app-name]</code> is a good naming convention.</li>
      <li>Save the credential.</li>
    </ol>
  </li>
  <li>Then in the AppService hosting your app, go to “Identity” and add the User Assigned Managed Identity you created in step 1.</li>
  <li>In the Environment Variables of your AppService, add the following variables:
    <ol>
      <li><code class="language-plaintext highlighter-rouge">AzureAd__ClientId</code> with the value of the ClientId of your App Registration (not the Managed Identity).</li>
      <li><code class="language-plaintext highlighter-rouge">AzureAd__TenantId</code> with the value of your TenantId.</li>
      <li><code class="language-plaintext highlighter-rouge">AzureAd__ClientCredentials__0__SourceType</code> = <code class="language-plaintext highlighter-rouge">SignedAssertionFromManagedIdentity</code></li>
      <li><code class="language-plaintext highlighter-rouge">AzureAd__ClientCredentials__0__ManagedIdentityClientId</code> = ClientId of the Managed Identity (from step 1.2) (⚠️ <strong>NOT</strong> the App Registration’s ClientId).</li>
      <li>Remove any existing <code class="language-plaintext highlighter-rouge">ClientSecret</code> configuration if you have it, usually <code class="language-plaintext highlighter-rouge">AzureAd__ClientSecret</code> or similar.</li>
      <li>Apply the changes (restart the app if needed).</li>
    </ol>
  </li>
  <li>Your app should now use the federated credential to authenticate with EntraID without any client secrets!</li>
</ol>

<h2 id="fin">Fin</h2>

<p>There are more docs on this process in the <a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-config-app-trust-managed-identity?tabs=microsoft-entra-admin-center%2Cdotnet#microsoftidentityweb">Microsoft docs</a>. My most important learning was that using the SYSTEM Assigned Credential did not work as they do not get assigned a ClientId, so we cannot use them for the ManagedIdentityClientId configuration. Also the <code class="language-plaintext highlighter-rouge">AzureAd__ClientCredentials__0__SourceType</code>, the <code class="language-plaintext highlighter-rouge">__0__</code> is used to place the item as an object in array position 0 of the <code class="language-plaintext highlighter-rouge">ClientCredentials</code> JSON array. If you use the <code class="language-plaintext highlighter-rouge">appsettings.json</code> file instead of environment variables, it would look like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"AzureAd"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"ClientId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-app-registration-client-id"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"TenantId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-tenant-id"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ClientCredentials"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"SourceType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SignedAssertionFromManagedIdentity"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"ManagedIdentityClientId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"your-managed-identity-client-id"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>I hope this guide lets you avoid storing secrets for this! No more expired or leaked secrets! 🔐</p>

<h2 id="end">End</h2>

<p>// Nils Henrik
// Human written, AI reviewed</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="azure" /><category term="security" /><summary type="html"><![CDATA[This is a guide to help you avoid using client secrets in Azure when authenticating with EntraID (Azure AD) and Microsoft Identity Web.]]></summary></entry><entry><title type="html">Require Auth for OpenAPI and Scalar/Swagger in .NET 10 APIs</title><link href="https://hals.app/blog/dotnet-openapi-scalar-security/" rel="alternate" type="text/html" title="Require Auth for OpenAPI and Scalar/Swagger in .NET 10 APIs" /><published>2026-02-16T00:00:00+00:00</published><updated>2026-02-16T00:00:00+00:00</updated><id>https://hals.app/blog/dotnet-openapi-scalar-security</id><content type="html" xml:base="https://hals.app/blog/dotnet-openapi-scalar-security/"><![CDATA[<p>How to secure the OpenApi schema and Scalar browser in a Microsoft Asp Net Core Web Api using Microsoft Identity Web for authentication.</p>

<h2 id="intro">Intro</h2>

<p>In my <a href="/blog/dotnet-openapi-scalar-oauth2/">previous post</a> I showed how to setup OpenApi and Scalar in a Microsoft Asp Net Core Web Api. In this post I will show how to secure the OpenApi schema and Scalar browser using Microsoft Identity Web for authentication.</p>

<p>The OpenApi schema and Scalar browser can contain sensitive information about your API, such as the available endpoints, full documentation, request and response schemas, and authentication requirements.</p>

<p>Microsoft <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi#configure-openapi-document-generation">recommends</a> OpenApi enabled only in development environments.
But securing the OpenApi schema allows you to safely expose these resources in production environments as well, without risking unintended information disclosure to unauthorized users.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>A dotnet Asp Net Core Web Api running .NET 10.</p>

<p>Existing authorization setup with OAuth2 (I’m using <code class="language-plaintext highlighter-rouge">Microsoft.Identity.Web</code> in this guide)</p>

<p>Install these nuget packages:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Scalar.AspNetCore</code> (I used <code class="language-plaintext highlighter-rouge">v2.12.41</code>) (Note, alternatives like <code class="language-plaintext highlighter-rouge">swagger-ui</code> may also work.)</li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.OpenApi</code> (I used <code class="language-plaintext highlighter-rouge">v10.0.3</code>)</li>
</ul>

<h2 id="setting-up-openapi-and-scalar">Setting up OpenApi and Scalar</h2>

<p>See the guide in my previous post: <a href="/blog/dotnet-openapi-scalar-oauth2/">Microsoft OpenApi Setup with Scalar</a></p>

<h2 id="authentication-configuration">Authentication configuration</h2>

<p>Assuming your website has setup authentication with Microsoft Identity Web, your setup looks something like this:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddMicrosoftIdentityWebApiAuthentication</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">EnableTokenAcquisitionToCallDownstreamApi</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">AddDownstreamApi</span><span class="p">(</span>
        <span class="n">DownstreamApiService</span><span class="p">.</span><span class="n">DownstreamApiServiceName</span><span class="p">,</span>
        <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span>
            <span class="s">$"DownstreamApis:</span><span class="p">{</span><span class="n">DownstreamApiConfig</span><span class="p">.</span><span class="n">DownstreamApiName</span><span class="p">}</span><span class="s">"</span>
        <span class="p">)</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nf">AddInMemoryTokenCaches</span><span class="p">();</span>

<span class="c1">// Snip ...</span>

<span class="kt">var</span> <span class="n">app</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
<span class="c1">// Snip ...</span>

<span class="n">app</span><span class="p">.</span><span class="nf">UseAuthentication</span><span class="p">();</span>
<span class="n">app</span><span class="p">.</span><span class="nf">UseAuthorization</span><span class="p">();</span>
<span class="c1">// Snip ...</span>

<span class="n">app</span><span class="p">.</span><span class="nf">MapOpenApi</span><span class="p">();</span>
<span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="s">"/scalar"</span><span class="p">,</span> <span class="n">options</span> <span class="p">=&gt;</span> <span class="p">...);</span>
<span class="c1">// Snip ...</span>
</code></pre></div></div>

<h3 id="adding-multiple-authentication-schemes">Adding multiple Authentication Schemes</h3>

<p>As the WebApi authentication setup is designed for APIs, it uses the JwtBearer authentication scheme by default. This is not suitable for authenticating users in the browser, as it does not support interactive login flows. To authenticate users in the browser, we need to use the OpenIdConnect authentication scheme, which is designed for web applications and supports interactive login flows. This is easily added by adding the <code class="language-plaintext highlighter-rouge">AddMicrosoftIdentityWebAppAuthentication</code> method to the services, which adds the OpenIdConnect authentication scheme.</p>

<p>First out:</p>

<p>Add a <code class="language-plaintext highlighter-rouge">callbackPath</code> to your authentication setup.</p>

<pre><code class="language-json5">// in your appsettings.json
"AzureAd": {
  // Snip other AzureAd config
  "CallbackPath": "/signin-oidc",
}
</code></pre>

<p class="notice--info">You may have to add redirect URIs in your Azure AD app registration for the OpenIdConnect authentication to work. The redirect URI should be in the format <code class="language-plaintext highlighter-rouge">https://&lt;your-api-base-url&gt;/signin-oidc</code>.</p>

<p>Then add the <code class="language-plaintext highlighter-rouge">AddMicrosoftIdentityWebAppAuthentication</code> (NOTE: App, not Api) to your services:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddMicrosoftIdentityWebApiAuthentication</span><span class="p">(</span>
    <span class="c1">// Snip (see above)</span>
<span class="p">)</span>

<span class="c1">// Note: IdentityWebApp, not Api! Otherwise identical to the Api setup (You may not require downstream api setup here though, depending on your use-case)</span>
<span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddMicrosoftIdentityWebAppAuthentication</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">EnableTokenAcquisitionToCallDownstreamApi</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">AddDownstreamApi</span><span class="p">(</span>
        <span class="n">DownstreamApiService</span><span class="p">.</span><span class="n">DownstreamApiServiceName</span><span class="p">,</span>
        <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetSection</span><span class="p">(</span>
            <span class="s">$"DownstreamApis:</span><span class="p">{</span><span class="n">DownstreamApiConfig</span><span class="p">.</span><span class="n">DownstreamApiName</span><span class="p">}</span><span class="s">"</span>
        <span class="p">)</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nf">AddInMemoryTokenCaches</span><span class="p">();</span>
</code></pre></div></div>

<p>We now have two authentication schemes, one for the API and one for the Web App. The Web App scheme is used by Scalar to authenticate users in the browser, and the API scheme is used to authenticate requests to the API.</p>

<p>We want to use the Api authentication for most routes, so we setup a default authentication scheme:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="nf">AddAuthorization</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span><span class="p">.</span><span class="n">FallbackPolicy</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AuthorizationPolicyBuilder</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">AddAuthenticationSchemes</span><span class="p">(</span>
            <span class="n">OpenIdConnectDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">,</span> <span class="c1">// Optional: Allows us to use the OpenIdConnect cookie if authenticated</span>
            <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span> <span class="c1">// This is the default scheme used for API authentication</span>
        <span class="p">)</span>
        <span class="p">.</span><span class="nf">RequireAuthenticatedUser</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Now we setup the Scalar Auth Policy: This has the OpenIdConnect scheme as default,
which will cause it to redirect to the Azure AD login page when authentication is required.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">scalarUiAuthenticationPolicy</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">AuthorizationPolicyBuilder</span><span class="p">()</span>
    <span class="p">.</span><span class="nf">AddAuthenticationSchemes</span><span class="p">(</span>
        <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">,</span>
        <span class="n">OpenIdConnectDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span> <span class="c1">// Intentionally use OpenIdConnect last as it will redirect to Azure AD login page.</span>
    <span class="p">)</span>
    <span class="p">.</span><span class="nf">AddRequirements</span><span class="p">(</span><span class="k">new</span> <span class="nf">DenyAnonymousAuthorizationRequirement</span><span class="p">())</span>
    <span class="p">.</span><span class="nf">Build</span><span class="p">();</span>
</code></pre></div></div>

<p>Then we have to setup the OpenApi schema to use the Web App authentication scheme, and require the correct scopes.</p>

<p>Add “RequireAuthorization” to the OpenApi endpoint to require authentication
for the OpenApi document itself. This uses the “default” authentication scheme,
which we set to the API scheme above.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapOpenApi</span><span class="p">().</span><span class="nf">RequireAuthorization</span><span class="p">(</span><span class="n">scalarUiAuthenticationPolicy</span><span class="p">);</span>
</code></pre></div></div>

<p>Finally we add the RequireAuthorization to the Scalar endpoint</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
    <span class="p">{</span>
        <span class="c1">// Snip Scalar setup (see previous post)</span>
    <span class="p">})</span>
    <span class="p">.</span><span class="nf">RequireAuthorization</span><span class="p">(</span><span class="n">scalarUiAuthenticationPolicy</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="fin">Fin</h2>

<p>Good!</p>

<p>Now you should be able to run the API locally and see that a login is required to access the OpenApi document and the Scalar UI.</p>

<h2 id="end">End</h2>

<p>// Nils Henrik</p>

<p>This post was written by a human.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="dotnet" /><category term="azure" /><category term="security" /><summary type="html"><![CDATA[How to secure the OpenApi schema and Scalar browser in a Microsoft Asp Net Core Web Api using Microsoft Identity Web for authentication.]]></summary></entry><entry><title type="html">Scroll Faster using CapsLock and Autohotkey</title><link href="https://hals.app/blog/capslock-scroll-faster/" rel="alternate" type="text/html" title="Scroll Faster using CapsLock and Autohotkey" /><published>2025-11-22T00:00:00+00:00</published><updated>2025-11-22T00:00:00+00:00</updated><id>https://hals.app/blog/capslock-scroll-faster</id><content type="html" xml:base="https://hals.app/blog/capslock-scroll-faster/"><![CDATA[<p>I wanted a global faster scrolling option in windows.</p>

<p>In apps like VSCode when you hold ALT the Scroll Wheel on your mouse scrolls about 5 times faster.</p>

<p>I wanted this functionality in every app. Long websites, word documents, pdfs etc.</p>

<p>To achieve this I created this AutoHotKey script.</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#Requires AutoHotkey v2.0
</span>
<span class="c">#SingleInstance Force
</span>
<span class="c">; Fast scrolling when Caps Lock is held down
</span>
<span class="err">SetCapsLockState</span> <span class="err">("AlwaysOff")</span> <span class="c">; Disable Standard Caps Lock functionality (Actual caps-lock will not work any more)
</span>
<span class="err">scrollSpeedMultiplier</span> <span class="err">:=</span> <span class="err">5</span> <span class="c">; Scroll speed multiplier
</span>
<span class="c">; Define fast scroll behavior when Caps Lock is held down
</span><span class="err">CapsLock</span> <span class="err">&amp;</span> <span class="err">WheelUp::</span>
<span class="err">{</span>
    <span class="err">Send("{WheelUp</span> <span class="err">"</span> <span class="err">scrollSpeedMultiplier</span> <span class="err">"}")</span>
    <span class="err">return</span>
<span class="err">}</span>

<span class="err">CapsLock</span> <span class="err">&amp;</span> <span class="err">WheelDown::</span>
<span class="err">{</span>
    <span class="err">Send("{WheelDown</span> <span class="err">"</span> <span class="err">scrollSpeedMultiplier</span> <span class="err">"}")</span>
    <span class="err">return</span>
<span class="err">}</span>

</code></pre></div></div>

<p>This script totally disables the capslock, but replaces it to be a modern modifier key.</p>

<p>Save your file as <code class="language-plaintext highlighter-rouge">CapsScrollSpeed.ahk</code> and add it to your startup folder to auto enable the script on every startup. The startup folder can be found by entering entering <code class="language-plaintext highlighter-rouge">shell:startup</code> in the “URL” bar in Explorer.</p>

<h2 id="end">End</h2>

<p>Thats it for this time.</p>

<p>// Nils Henrik</p>

<p>This post was written by a human.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[I wanted a global faster scrolling option in windows.]]></summary></entry><entry><title type="html">Microsoft OpenApi OAuth2 Setup with Scalar</title><link href="https://hals.app/blog/dotnet-openapi-scalar-oauth2/" rel="alternate" type="text/html" title="Microsoft OpenApi OAuth2 Setup with Scalar" /><published>2025-11-19T00:00:00+00:00</published><updated>2025-11-19T00:00:00+00:00</updated><id>https://hals.app/blog/dotnet-openapi-scalar-oauth2</id><content type="html" xml:base="https://hals.app/blog/dotnet-openapi-scalar-oauth2/"><![CDATA[<p>A quick guide to setting up OpenApi for OAuth2 (with Azure AD / EntraID) and integrating it with the <a href="https://scalar.com">Scalar</a> openapi browser.</p>

<h2 id="prerequisites">Prerequisites</h2>

<p>A dotnet Web Api running .NET 10.</p>

<p>Existing authorization setup with OAuth2 (I’m using <code class="language-plaintext highlighter-rouge">Microsoft.Identity.Web</code> in this guide)</p>

<p>Install these nuget packages:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Scalar.AspNetCore</code> (I used <code class="language-plaintext highlighter-rouge">v2.10.3</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">Microsoft.AspNetCore.OpenApi</code> (I used <code class="language-plaintext highlighter-rouge">v10.0.0</code>)</li>
</ul>

<h2 id="setting-up-openapi-and-scalar">Setting up OpenApi and Scalar</h2>

<p>Following the Microsoft docs: <a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview">https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview</a> and the Scalar docs:
<a href="https://guides.scalar.com/scalar/scalar-api-references/integrations/net-aspnet-core/integration">https://guides.scalar.com/scalar/scalar-api-references/integrations/net-aspnet-core/integration</a></p>

<p>Add the OpenApi service (this registers the code to generate the OpenApi schema):</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddOpenApi</span><span class="p">();</span>
</code></pre></div></div>

<p>And register routes to serve the generated json:
Also register the Scalar api here.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">app</span><span class="p">.</span><span class="n">Environment</span><span class="p">.</span><span class="nf">IsDevelopment</span><span class="p">())</span>
<span class="p">{</span>
    <span class="n">app</span><span class="p">.</span><span class="nf">MapOpenApi</span><span class="p">();</span> <span class="c1">// Expose the openapi json</span>
    <span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="s">"/scalar"</span><span class="p">);</span> <span class="c1">// Enable Scalar on the "/scalar" route</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You should now be able to start your API and find a OpenApi json at <code class="language-plaintext highlighter-rouge">/openapi/v1.json</code> and to view it visually in the <code class="language-plaintext highlighter-rouge">/scalar</code> route.</p>

<h2 id="configuring-oauth2">Configuring OAuth2</h2>

<p>To configure OAuth2 for your API you need to do two things:</p>

<ol>
  <li>Alter the OpenApi schema with custom changes.</li>
  <li>Setup Scalar to recognize the Authorization scheme, and configure some extra steps.</li>
</ol>

<h3 id="altering-the-open-api">Altering the Open Api</h3>

<p>We need to change the OpenApi initialization and add a <em>Transformer</em></p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddOpenApi</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span><span class="p">.</span><span class="n">AddDocumentTransformer</span><span class="p">&lt;</span><span class="n">CustomOpenApiTransformer</span><span class="p">&gt;();</span>
<span class="p">});</span>
</code></pre></div></div>

<p>The transformer is code to adjust the generated OpenApi schema. A short example of what a class could look like is below.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CustomOpenApiTransformer</span><span class="p">(</span><span class="n">IConfiguration</span> <span class="n">configuration</span><span class="p">)</span> <span class="p">:</span> <span class="n">IOpenApiDocumentTransformer</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Task</span> <span class="nf">TransformAsync</span><span class="p">(</span>
        <span class="n">OpenApiDocument</span> <span class="n">document</span><span class="p">,</span>
        <span class="n">OpenApiDocumentTransformerContext</span> <span class="n">context</span><span class="p">,</span>
        <span class="n">CancellationToken</span> <span class="n">cancellationToken</span>
    <span class="p">)</span>
    <span class="p">{</span>
        <span class="n">document</span><span class="p">.</span><span class="n">Info</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiInfo</span>
        <span class="p">{</span>
            <span class="n">Title</span> <span class="p">=</span> <span class="s">"Echo AyelixProxyApi"</span>
        <span class="p">};</span>

        <span class="c1">// Code from below should be placed here...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here we need to add two parts:</p>

<ol>
  <li>A SecuritySchema</li>
  <li>A SecurityRequirement</li>
</ol>

<h4 id="securityschema">SecuritySchema</h4>

<p>The security schema describes a specific security setup.</p>

<p>For a dotnet api using Azure AD and OAuth2 this should be:</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">authority</span> <span class="p">=</span> <span class="n">configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"AzureAd:Authority"</span><span class="p">);</span> <span class="c1">// Learn more here https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration#authority</span>
<span class="kt">var</span> <span class="n">audience</span> <span class="p">=</span> <span class="n">configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"AzureAd:Audience"</span><span class="p">);</span> <span class="c1">// In this example this is the guid Client Id of the Api</span>
<span class="kt">var</span> <span class="n">schemaKey</span> <span class="p">=</span> <span class="s">"OAuth2"</span><span class="p">;</span> <span class="c1">// This name is used as a key, and could be anything as long as you are consistent.</span>
<span class="kt">var</span> <span class="n">scopes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="p">{</span>
        <span class="s">$"</span><span class="p">{</span><span class="n">audience</span><span class="p">}</span><span class="s">/.default"</span><span class="p">,</span> <span class="c1">// Actual Scope</span>
        <span class="s">"All scopes"</span> <span class="c1">// Human readable description</span>
    <span class="p">},</span>
<span class="p">};</span>
<span class="kt">var</span> <span class="n">securitySchemes</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">IOpenApiSecurityScheme</span><span class="p">&gt;</span>
<span class="p">{</span>
    <span class="p">[</span><span class="n">schemaKey</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiSecurityScheme</span>
    <span class="p">{</span>
        <span class="n">Type</span> <span class="p">=</span> <span class="n">SecuritySchemeType</span><span class="p">.</span><span class="n">OAuth2</span><span class="p">,</span>
        <span class="n">Scheme</span> <span class="p">=</span> <span class="s">"OAuth2"</span><span class="p">,</span>
        <span class="n">Flows</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiOAuthFlows</span>
        <span class="p">{</span>
            <span class="n">Implicit</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiOAuthFlow</span>
            <span class="p">{</span>
                <span class="n">AuthorizationUrl</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">authority</span><span class="p">}</span><span class="s">/oauth2/v2.0/authorize"</span><span class="p">),</span>
                <span class="n">TokenUrl</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">authority</span><span class="p">}</span><span class="s">/oauth2/v2.0/token"</span><span class="p">),</span>
                <span class="n">RefreshUrl</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Uri</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">authority</span><span class="p">}</span><span class="s">/oauth2/v2.0/token"</span><span class="p">),</span>
                <span class="n">Scopes</span> <span class="p">=</span> <span class="n">scopes</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">},</span>
    <span class="p">},</span>
<span class="p">};</span>
<span class="n">document</span><span class="p">.</span><span class="n">Components</span> <span class="p">??=</span> <span class="k">new</span> <span class="nf">OpenApiComponents</span><span class="p">();</span>
<span class="n">document</span><span class="p">.</span><span class="n">Components</span><span class="p">.</span><span class="n">SecuritySchemes</span> <span class="p">=</span> <span class="n">securitySchemes</span><span class="p">;</span>
</code></pre></div></div>

<p>Above we defined a schema, now we have to require this schema for the endpoints.</p>

<h4 id="securityrequirement">SecurityRequirement</h4>

<p>A security requirement can be placed on every endpoint, or on the entire document. For our use-case we just put it on everything, and require the same scope for all endpoints. This could be adjusted according to your api design.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">securityRequirement</span> <span class="p">=</span> <span class="k">new</span> <span class="n">OpenApiSecurityRequirement</span>
<span class="p">{</span>
    <span class="p">[</span><span class="k">new</span> <span class="nf">OpenApiSecuritySchemeReference</span><span class="p">(</span><span class="n">schemaKey</span><span class="p">,</span> <span class="n">document</span><span class="p">)]</span> <span class="p">=</span> <span class="p">[..</span><span class="n">scopes</span><span class="p">.</span><span class="n">Keys</span><span class="p">],</span>
<span class="p">};</span>

<span class="n">document</span><span class="p">.</span><span class="n">Security</span> <span class="p">=</span> <span class="p">[</span><span class="n">securityRequirement</span><span class="p">];</span>
</code></pre></div></div>

<p>Thats all. Insert this code in the <code class="language-plaintext highlighter-rouge">TransformAsync</code> method.</p>

<p>You now have the setup and your openapi.json file should have all the required mapping.</p>

<h3 id="setup-scalar">Setup Scalar</h3>

<p>Finally we need to configure Scalar with the expected ClientId to use for the Web frontend.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">app</span><span class="p">.</span><span class="nf">MapScalarApiReference</span><span class="p">(</span><span class="s">"/scalar"</span><span class="p">,</span> <span class="n">options</span> <span class="p">=&gt;</span>
<span class="p">{</span>
    <span class="n">options</span>
        <span class="p">.</span><span class="nf">AddPreferredSecuritySchemes</span><span class="p">(</span><span class="s">"OAuth2"</span><span class="p">)</span> <span class="c1">// This is the schemaKey from above</span>
        <span class="p">.</span><span class="nf">AddImplicitFlow</span><span class="p">(</span>
            <span class="s">"OAuth2"</span><span class="p">,</span> <span class="c1">// Again: schemaKey</span>
            <span class="n">flow</span> <span class="p">=&gt;</span>
            <span class="p">{</span>
                <span class="n">flow</span><span class="p">.</span><span class="n">ClientId</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"Scalar:OAuthClientId"</span><span class="p">);</span> <span class="c1">// The ClientId to use. Can usually be `AzureAd:ClientId`</span>
                <span class="kt">var</span> <span class="n">audience</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Configuration</span><span class="p">.</span><span class="nf">GetOrThrow</span><span class="p">(</span><span class="s">"AzureAd:Audience"</span><span class="p">);</span>
                <span class="n">flow</span><span class="p">.</span><span class="n">SelectedScopes</span> <span class="p">=</span> <span class="p">[</span><span class="s">$"</span><span class="p">{</span><span class="n">audience</span><span class="p">}</span><span class="s">/.default"</span><span class="p">];</span> <span class="c1">// Same scopes as defined in the OpenApi transformer!</span>
            <span class="p">}</span>
        <span class="p">)</span>
<span class="p">});</span>
</code></pre></div></div>

<p class="notice--info">You may need to add a redirect Uri to your configuration now. By default the redirect Url should be <code class="language-plaintext highlighter-rouge">http://localhost/scalar</code> (Redirect uris to localhost do not have to include the port)</p>

<p>That’s all!</p>

<p>You can now browse and authenticate to your API in a quick little local setup!</p>

<h2 id="end">End</h2>

<p>// Nils Henrik</p>

<p>This post was written by a human.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="dotnet" /><category term="azure" /><summary type="html"><![CDATA[A quick guide to setting up OpenApi for OAuth2 (with Azure AD / EntraID) and integrating it with the Scalar openapi browser.]]></summary></entry><entry><title type="html">Fixing Crash on Resume from Sleep in Bluefin 42</title><link href="https://hals.app/blog/bluefin-bluetooth/" rel="alternate" type="text/html" title="Fixing Crash on Resume from Sleep in Bluefin 42" /><published>2025-09-28T00:00:00+00:00</published><updated>2025-09-28T00:00:00+00:00</updated><id>https://hals.app/blog/bluefin-bluetooth</id><content type="html" xml:base="https://hals.app/blog/bluefin-bluetooth/"><![CDATA[<p>My Bluefin Linux crashed on resume from Suspend/sleep.</p>

<p>The cause seems to have been the bluetooth adapter, as disabling the bluetooth before sleeping avoided the issue. You should test if disabling bluetooth manually fixes sleep resume for you. If disabling bluetooth actually fixes sleep this guide should help!</p>

<p>The guide was used on <code class="language-plaintext highlighter-rouge">Bluefin 42.20250928.1</code> with the kernel version <code class="language-plaintext highlighter-rouge">Linux 6.15.10-200.fc42.x86_64</code>. I believe the guide should be compatible with all universal-blue variants if needed.</p>

<p>A fix on the net was suggested to disable the bluetooth on sleep, and re-enable on resume. As I’m not that familiar with the systemd services I have documented the exact steps to follow below.
(Suggested here <a href="https://www.reddit.com/r/Fedora/comments/1g7ke8e/comment/ly84xrf/">https://www.reddit.com/r/Fedora/comments/1g7ke8e/comment/ly84xrf/</a>)</p>

<p>The commands and paths needed for me were:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nano /etc/systemd/system/bt-sleep-fix.service
</code></pre></div></div>

<p>This opens the nano text editor.</p>

<p>In there paste this file:</p>

<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[<span class="n">Unit</span>]
<span class="n">Description</span>=<span class="n">Disable</span> <span class="n">Bluetooth</span> <span class="n">before</span> <span class="n">going</span> <span class="n">to</span> <span class="n">sleep</span>
<span class="n">Before</span>=<span class="n">sleep</span>.<span class="n">target</span>
<span class="n">StopWhenUnneeded</span>=<span class="n">yes</span>

[<span class="n">Service</span>]
<span class="n">Type</span>=<span class="n">oneshot</span>
<span class="n">RemainAfterExit</span>=<span class="n">yes</span>

<span class="n">ExecStart</span>=/<span class="n">usr</span>/<span class="n">sbin</span>/<span class="n">rfkill</span> <span class="n">block</span> <span class="n">bluetooth</span>
<span class="n">ExecStop</span>=/<span class="n">usr</span>/<span class="n">sbin</span>/<span class="n">rfkill</span> <span class="n">unblock</span> <span class="n">bluetooth</span>

[<span class="n">Install</span>]
<span class="n">WantedBy</span>=<span class="n">sleep</span>.<span class="n">target</span>

</code></pre></div></div>

<p>Save the file as <code class="language-plaintext highlighter-rouge">bt-sleep-fix.service</code></p>

<p>Apply <code class="language-plaintext highlighter-rouge">chmod 644</code> to it (makes the file readable by all users on the system)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo chmod </span>644 /etc/systemd/system/bt-sleep-fix.service
</code></pre></div></div>

<p>Now we need to register the service with systemd:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start bt-sleep-fix.service
systemctl <span class="nb">enable </span>bt-sleep-fix.service
</code></pre></div></div>

<p>This should now enable and disable the bluetooth adapter automatically on sleep and resume. This avoids the crash on resume for me! Nice!</p>

<h2 id="end">End</h2>

<p>Thats it! I hope this guide helps you get your system resuming from sleep, while keeping bluetooth capabilities!</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[My Bluefin Linux crashed on resume from Suspend/sleep.]]></summary></entry><entry><title type="html">Flåklypa Grand Prix (2000) on Windows 10+ and Mac</title><link href="https://hals.app/blog/fgp-modern/" rel="alternate" type="text/html" title="Flåklypa Grand Prix (2000) on Windows 10+ and Mac" /><published>2024-12-28T00:00:00+00:00</published><updated>2024-12-28T00:00:00+00:00</updated><id>https://hals.app/blog/fgp-modern</id><content type="html" xml:base="https://hals.app/blog/fgp-modern/"><![CDATA[<p>The Flåklypa Grand Prix videogame, originally released in 2000, holds a cherished place in the hearts of Norwegians. Its engaging minigames have defined a generation, with over 400,000 copies sold. In 2002, a “Golden Edition” (Gullutgave) was released, further cementing its legacy.</p>

<p>However, these classic games are not officially compatible with modern systems like Windows 10 and 11 and MacOS, leaving fans longing for a way to relive their childhood memories on secure, up-to-date PCs.</p>

<p>This post will guide you step-by-step through the process of running Flåklypa Grand Prix Gullutgave on both macOS and modern Windows systems. Please note, you’ll need to own the original Flåklypa Grand Prix Gullutgave CDs and a USB CD drive.</p>

<p>Let’s dive into the setup process so you can rediscover the nostalgic magic of Flåklypa Grand Prix!</p>

<h2 id="mac-setup-guide-using-whisky-on-macos">Mac Setup Guide (Using Whisky on macOS)</h2>

<p>Follow these steps to install and run <strong>Flåklypa Grand Prix Gullutgave</strong> on a Mac (M1+) using <strong>Wine</strong> with <strong>Whisky</strong>.</p>

<h3 id="1-prepare-the-game-files">1. Prepare the Game Files</h3>

<ol>
  <li>Insert your original <strong>Flåklypa Gullutgave</strong> CDs into a USB CD drive.</li>
  <li>Copy all files from the CDs to two folders on your Mac. Ensure the folders are organized as follows:
    <ul>
      <li><strong>DISC_1/</strong>: Files from the white CD.</li>
      <li><strong>DISC_2/</strong>: Files from the second CD.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="2-download-required-files">2. Download Required Files</h3>

<ol>
  <li>
    <p><strong>Update Patch 12</strong>:</p>

    <ul>
      <li>Download the <code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code> update patch from this site: <a href="https://sites.google.com/view/flaklypafix">FlåklypaFix</a>.</li>
    </ul>
  </li>
  <li>
    <p><strong>NO-CD Patch</strong>:</p>

    <ul>
      <li>Download a NO-CD patch for Flåklypa Gullutgave 12. One example is hosted on the Internet Archive: <a href="https://web.archive.org/web/20241226130042/https://www.old-games.ru/forum/attachments/fgp-upd12-7z.249506/">NO-CD Patch</a>.</li>
      <li>Extract the file and place the <code class="language-plaintext highlighter-rouge">FGP.exe</code> into a folder named <code class="language-plaintext highlighter-rouge">no-cd/</code>.</li>
    </ul>
  </li>
  <li>
    <p><strong>dgVoodoo2 Compatibility Fix</strong>:</p>
    <ul>
      <li>Download version <code class="language-plaintext highlighter-rouge">2.79.x</code> of dgVoodoo2 from <a href="https://github.com/legluondunet/mlls-tools/blob/master/dgVoodoo2/dgVoodoo2_79_3.zip">this repository</a> or from <a href="https://github.com/lutris/dgvoodoo2/releases/download/v2.79.1/dgvoodoo2-v2.79.1.tar.xz">this Lutris release page</a>.</li>
      <li>Extract the files and copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">/MS/x86/</code> folder into a new folder named <code class="language-plaintext highlighter-rouge">compability-fix/</code>.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="3-install-whisky">3. Install Whisky</h3>

<ol>
  <li>
    <p>Download and install <strong>Whisky</strong> from <a href="https://getwhisky.app">getwhisky.app</a>.</p>

    <ul>
      <li>This guide is tested on Whisky version <code class="language-plaintext highlighter-rouge">v2.3.4</code>.</li>
    </ul>
  </li>
  <li>
    <p>Open Whisky and create a new “Bottle”:</p>
    <ul>
      <li>Set the Windows version to <strong>Windows XP</strong>.</li>
      <li>Wait a moment for the image to load (this may take up to a minute).</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="4-configure-the-bottle">4. Configure the Bottle</h3>

<ol>
  <li>
    <p>Open the Bottle settings and click <strong>Winetricks…</strong>:</p>

    <ul>
      <li>Go to the <strong>DLLs</strong> tab.</li>
      <li>Find <code class="language-plaintext highlighter-rouge">dsound</code> and click <strong>Run</strong> to install it.</li>
    </ul>
  </li>
  <li>
    <p>Configure Wine:</p>

    <ul>
      <li>Click <strong>Bottle Configuration</strong> → <strong>Open Wine Configuration</strong>.</li>
      <li>Go to the <strong>Libraries</strong> tab and add the following overrides:
        <ul>
          <li><code class="language-plaintext highlighter-rouge">security</code></li>
          <li><code class="language-plaintext highlighter-rouge">ddraw</code> (Ignore the performance warning.)</li>
          <li><code class="language-plaintext highlighter-rouge">d3dimm</code></li>
        </ul>
      </li>
      <li>Press <strong>OK</strong> to save the settings.</li>
    </ul>
  </li>
  <li>
    <p>Enable DXVK:</p>
    <ul>
      <li>Go back to <strong>Bottle Configuration</strong> and enable <strong>DXVK</strong>.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="5-prepare-the-game-files-in-the-bottle">5. Prepare the Game Files in the Bottle</h3>

<ol>
  <li>Open the Bottle’s <strong>C Drive</strong>:
    <ul>
      <li>Click <strong>Open C Drive</strong> in the Bottle Configuration.</li>
      <li>Copy the following folders into the <code class="language-plaintext highlighter-rouge">C:/</code> directory of the Bottle:
        <ul>
          <li><code class="language-plaintext highlighter-rouge">DISC_1/</code></li>
          <li><code class="language-plaintext highlighter-rouge">DISC_2/</code></li>
          <li><code class="language-plaintext highlighter-rouge">patch12/</code> (containing <code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code>)</li>
          <li><code class="language-plaintext highlighter-rouge">no-cd/</code> (containing the NO-CD patched <code class="language-plaintext highlighter-rouge">FGP.exe</code>)</li>
          <li><code class="language-plaintext highlighter-rouge">compability-fix/</code> (containing <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, <code class="language-plaintext highlighter-rouge">d3dimm.dll</code>).</li>
        </ul>
      </li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="6-install-the-game">6. Install the Game</h3>

<ol>
  <li>Run the installer:
    <ul>
      <li>In Whisky, click <strong>Run…</strong> and navigate to <code class="language-plaintext highlighter-rouge">C:/DISC_1/setup.exe</code>.</li>
      <li>Follow the installation steps, leaving all settings at their default values.</li>
      <li><strong>Important:</strong> Do not start the game when the installation finishes.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="7-apply-update-patch-12">7. Apply Update Patch 12</h3>

<ol>
  <li>Run the patch installer:
    <ul>
      <li>Click <strong>Run…</strong> again and navigate to <code class="language-plaintext highlighter-rouge">C:/patch12/FGPGOLD_UPD12.exe</code>.</li>
      <li>Follow the installation steps. <strong>Do not start the game</strong> when finished.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="8-apply-the-no-cd-patch">8. Apply the NO-CD Patch</h3>

<ol>
  <li>Replace the game executable:
    <ul>
      <li>Navigate to the game installation folder: <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>.</li>
      <li>Copy the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file from the <code class="language-plaintext highlighter-rouge">no-cd/</code> folder and replace the existing <code class="language-plaintext highlighter-rouge">FGP.exe</code> file in the game folder.</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="9-apply-compatibility-fixes">9. Apply Compatibility Fixes</h3>

<ol>
  <li>Copy dgVoodoo2 files:
    <ul>
      <li>Copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">compability-fix/</code> folder into the game installation folder (<code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>).</li>
      <li>Ensure these files are in the same folder as <code class="language-plaintext highlighter-rouge">FGP.exe</code> (not in subfolders).</li>
    </ul>
  </li>
</ol>

<hr />

<h3 id="10-pin-the-game-in-whisky">10. Pin the Game in Whisky</h3>

<ol>
  <li>
    <p>Pin the game executable:</p>

    <ul>
      <li>Go to the Bottle settings and click <strong>Pin Program</strong>.</li>
      <li>Set the name to <strong>Flåklypa</strong>.</li>
      <li>Locate and select the game executable: <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/FGP.exe</code>.</li>
    </ul>
  </li>
  <li>
    <p>Start the game:</p>
    <ul>
      <li>Press the <strong>Play</strong> button in Whisky (green triangle).</li>
      <li>If the game does not start, right-click the pinned program and select <strong>Run…</strong>.</li>
    </ul>
  </li>
</ol>

<h3 id="known-issues-on-mac">Known Issues on Mac</h3>

<ol>
  <li>
    <p><strong>Animations may fail to load</strong>:</p>

    <ul>
      <li>If animations freeze, press <code class="language-plaintext highlighter-rouge">Space</code> to skip them, and then restart the animation.</li>
    </ul>
  </li>
  <li>
    <p><strong>Helicopter game freezes</strong>:</p>
    <ul>
      <li>The helicopter mini-game may experience periodic freezes. Unfortunately, there is no known fix for this.</li>
    </ul>
  </li>
</ol>

<p>The game should now be running!™️ Relive the nostalgic adventure of <strong>Flåklypa Grand Prix Gullutgave</strong>.</p>

<h2 id="windows-10-and-11-setup-guide">Windows 10 and 11 Setup Guide</h2>

<p>This guide outlines the process to install and run <strong>Flåklypa Grand Prix Gullutgave</strong> on modern Windows systems (Windows 10 and 11).</p>

<h3 id="1-prepare-the-game-files-1">1. Prepare the Game Files</h3>

<ol>
  <li>Insert your original <strong>Flåklypa Gullutgave</strong> CDs into a USB CD-drive.</li>
  <li>Copy all files from the CDs to two folders on your PC:
    <ul>
      <li><strong>DISC_1/</strong>: Copy files from the first (white) CD.</li>
      <li><strong>DISC_2/</strong>: Copy files from the second CD.</li>
    </ul>
  </li>
</ol>

<h3 id="2-download-required-files-1">2. Download Required Files</h3>

<ol>
  <li>
    <p><strong>Update Patch 12</strong>:</p>

    <ul>
      <li>Download the <code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code> update patch from this site: <a href="https://sites.google.com/view/flaklypafix">FlåklypaFix</a>.</li>
    </ul>
  </li>
  <li>
    <p><strong>NO-CD Patch</strong>:</p>

    <ul>
      <li>Download a NO-CD patch for Flåklypa Gullutgave 12. One example is hosted on the Internet Archive: <a href="https://web.archive.org/web/20241226130042/https://www.old-games.ru/forum/attachments/fgp-upd12-7z.249506/">NO-CD Patch</a>. Unzip the file and copy the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file to a new folder named <code class="language-plaintext highlighter-rouge">no-cd/</code>.</li>
    </ul>
  </li>
  <li>
    <p><strong>dgVoodoo2 Compatibility Fix</strong>:</p>
    <ul>
      <li>Download version <code class="language-plaintext highlighter-rouge">2.79.x</code> of dgVoodoo2 from <a href="https://github.com/legluondunet/mlls-tools/blob/master/dgVoodoo2/dgVoodoo2_79_3.zip">https://github.com/legluondunet/mlls-tools/blob/master/dgVoodoo2/dgVoodoo2_79_3.zip</a></li>
      <li>Extract the files and copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>from the folder, and <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">/MS/x86/</code> folder to a new folder named <code class="language-plaintext highlighter-rouge">compability-fix/</code>.</li>
    </ul>
  </li>
</ol>

<h3 id="3-install-the-game">3. Install the Game</h3>

<ol>
  <li>Run the <code class="language-plaintext highlighter-rouge">setup.exe</code> from <code class="language-plaintext highlighter-rouge">DISC_1/</code>:
    <ul>
      <li>Navigate to <code class="language-plaintext highlighter-rouge">DISC_1/setup.exe</code> and double-click to start the installer.</li>
      <li>Follow the installation steps, leaving all settings at their default values.</li>
      <li><strong>Important:</strong> Do not start the game when the installation finishes.</li>
    </ul>
  </li>
</ol>

<h3 id="4-apply-update-patch-12">4. Apply Update Patch 12</h3>

<ol>
  <li>Run the patch installer:
    <ul>
      <li>Navigate to the location where you downloaded the patch (<code class="language-plaintext highlighter-rouge">FGPGOLD_UPD12.exe</code>) and double-click to run it.</li>
      <li>Follow the installation steps. Do not start the game when finished.</li>
    </ul>
  </li>
</ol>

<h3 id="5-apply-the-no-cd-patch">5. Apply the NO-CD Patch</h3>

<ol>
  <li>Replace the game executable:
    <ul>
      <li>Navigate to the game installation folder (default is <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>).</li>
      <li>Copy the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file from the <code class="language-plaintext highlighter-rouge">no-cd/</code> folder and paste it into the game installation directory. Replace the existing <code class="language-plaintext highlighter-rouge">FGP.exe</code> file when prompted.</li>
    </ul>
  </li>
</ol>

<h3 id="6-apply-compatibility-fixes">6. Apply Compatibility Fixes</h3>

<ol>
  <li>Copy dgVoodoo2 files:
    <ul>
      <li>Copy <code class="language-plaintext highlighter-rouge">dgVoodoo.conf</code>, <code class="language-plaintext highlighter-rouge">ddraw.dll</code>, and <code class="language-plaintext highlighter-rouge">d3dimm.dll</code> from the <code class="language-plaintext highlighter-rouge">compability-fix/</code> folder into the game installation folder (<code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>).</li>
    </ul>
  </li>
</ol>

<h3 id="7-compatibility-settings-if-needed">7. Compatibility Settings (If Needed)</h3>

<p>If the game does not run after completing the steps above, try the following:</p>

<ol>
  <li>Locate the <code class="language-plaintext highlighter-rouge">FGP.exe</code> file in the installation folder.</li>
  <li>Right-click on <code class="language-plaintext highlighter-rouge">FGP.exe</code> and select <strong>Properties</strong>.</li>
  <li>Go to the <strong>Compatibility</strong> tab and:
    <ul>
      <li>Check <strong>Run this program in compatibility mode for:</strong> and select <strong>Windows XP (Service Pack 3)</strong>.</li>
      <li>Check <strong>Reduce color mode</strong> and set it to <strong>16-bit (65536) color</strong>.</li>
    </ul>
  </li>
  <li>Click <strong>Apply</strong> and <strong>OK</strong>.</li>
</ol>

<h3 id="8-start-the-game">8. Start the Game</h3>

<ol>
  <li>Open the Windows Start Menu.</li>
  <li>Search for <strong>Flåklypa Grand Prix</strong> and launch the game from the shortcut.</li>
</ol>

<h3 id="troubleshooting">Troubleshooting</h3>

<ul>
  <li>If the game still does not start, navigate to <code class="language-plaintext highlighter-rouge">C:/Program Files (x86)/Flåklypa Grand Prix/</code>, right-click <code class="language-plaintext highlighter-rouge">FGP.exe</code>, and select <strong>Run as Administrator</strong>.</li>
  <li>Some graphics issues may occur depending on your hardware, but the dgVoodoo2 wrapper should resolve most compatibility problems.</li>
</ul>

<h3 id="enjoy">Enjoy!</h3>

<p>Everything should now be working!™️ Enjoy the nostalgia of <strong>Flåklypa Grand Prix Gullutgave</strong>.</p>

<h2 id="why-does-the-game-work-now">Why does the game work now?</h2>

<p>Flåklypa is an old game that relies on DirectX 8 and Direct Draw. The game relies on some graphics driver quirks.</p>

<p>DirectX 8 and DirectDraw is not implemented the same in newer windows, so some older function have been removed. We use a genious wrapper called dgVoodoo2 that in practice re-implements the old features for newer Windows versions. Performance may not equal the original setup, but we get it working!</p>

<p>Additionally we have a NO-CD FGP.exe that removes the need for a CD-key. The latest FGP patch required a key you could get from the flåklypa website, but this site has not been available for many years.</p>

<h2 id="bonus-cheat-codes">Bonus Cheat Codes</h2>

<p>As these seem to be lost in time I write the cheat codes here. Cheat codes was a thing used in the ancient times before you paid to unlock everything in games.</p>

<p>Just type the cheat-code anywhere in the games menus to unlock various things. If the cheat is typed correctly there will be a sound and sometimes a popup window.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">NOMORETWEAKS</code>: Unlock all games, scenes and activities</li>
  <li><code class="language-plaintext highlighter-rouge">CVG</code> Unlock all activities</li>
  <li><code class="language-plaintext highlighter-rouge">ASSISTANCE</code>: Unlock all games</li>
  <li><code class="language-plaintext highlighter-rouge">BEDTIMESTORY</code> Unlock all story pages in the photo-book</li>
  <li><code class="language-plaintext highlighter-rouge">SHOWMEHEAVEN</code>: Unlock a single puzzle image</li>
  <li><code class="language-plaintext highlighter-rouge">BLOWMEAWAY</code>: Unlock a single new film</li>
  <li><code class="language-plaintext highlighter-rouge">NOMOREDISHES</code>: Receive 3 pieces for the Il Tempo Gigante</li>
  <li><code class="language-plaintext highlighter-rouge">GIMMETRACKS</code>: Unlock new Tracks for the races</li>
  <li><code class="language-plaintext highlighter-rouge">HOTWHEELS</code>: Unlock new cars for the races</li>
  <li><code class="language-plaintext highlighter-rouge">ZOOMIN</code>: Zoom in towards the cursor?</li>
  <li><code class="language-plaintext highlighter-rouge">ZOOMOUT</code>: Zoom back to normal zoom.</li>
</ul>

<h2 id="end">End</h2>

<p>Thats it! I hope this guide helps you get the game working.</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[The Flåklypa Grand Prix videogame, originally released in 2000, holds a cherished place in the hearts of Norwegians. Its engaging minigames have defined a generation, with over 400,000 copies sold. In 2002, a “Golden Edition” (Gullutgave) was released, further cementing its legacy.]]></summary></entry><entry><title type="html">KTX: Convert png cubemap to ktx2</title><link href="https://hals.app/blog/ktx-cubemap-compressed/" rel="alternate" type="text/html" title="KTX: Convert png cubemap to ktx2" /><published>2024-12-19T00:00:00+00:00</published><updated>2024-12-19T00:00:00+00:00</updated><id>https://hals.app/blog/ktx-cubemap-compressed</id><content type="html" xml:base="https://hals.app/blog/ktx-cubemap-compressed/"><![CDATA[<p>In this post I show you how to create a compressed ktx2 file from a set of cubemap images.</p>

<p>I used the compression to compress a uncompressed 360MB 4k skybox into a 0.5MB(!) file (using a very sparse space-skybox)</p>

<p>The ktx docs are complex to read and short on examples. I offer my solution here if anyone else needs to convert a cubemap to a basisu compressed ktx2 cubemap.</p>

<p>First install the ktx tools. I use windows and downloaded the installer from the “Releases” page on GitHub: <a href="https://github.com/KhronosGroup/KTX-Software/releases">Link</a> (I used v4.3.2)</p>

<p>The below command was all I needed. (You may have to scroll right to see the whole command, or just copy everything.)</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ktx v4.3.2</span>
ktx create <span class="nt">--format</span> R8G8B8_SRGB <span class="nt">--encode</span> basis-lz <span class="nt">--cubemap</span> ./right.png ./left.png ./top.png ./bottom.png ./front.png ./back.png output.ktx2
</code></pre></div></div>

<p>Breakdown of the command:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ktx create</code>: This is the command to create a new KTX file.</li>
  <li><code class="language-plaintext highlighter-rouge">--format R8G8B8_SRGB</code>: This specifies the format of the images. R8G8B8_SRGB means the images are in 8-bit RGB format with sRGB color space. (This is probably what you want.)</li>
  <li><code class="language-plaintext highlighter-rouge">--encode basis-lz</code>: This specifies the encoding and compression method to use, in this case, <code class="language-plaintext highlighter-rouge">basis-lz</code> for Basis Universal ETC1S compression. (You may also try <code class="language-plaintext highlighter-rouge">uastc</code> for higher visual quality if needed)</li>
  <li><code class="language-plaintext highlighter-rouge">--cubemap</code>: This flag indicates that the images are to be used as a cubemap.</li>
  <li><code class="language-plaintext highlighter-rouge">./right.png ./left.png ./top.png ./bottom.png ./front.png ./back.png</code>: These are the input images for the cubemap, representing the six faces of the cube as inputs.</li>
  <li><code class="language-plaintext highlighter-rouge">output.ktx2</code>: The last parameter is the name of the output KTX2 file.</li>
</ul>

<p>You may want to decide which encoding you want to use, check the docs for variants.</p>

<p>Thats all for this time.</p>

<p>Note: If you have HDR images this is not yet supported as of writing. But it seems like <code class="language-plaintext highlighter-rouge">UASTC HDR</code> support should be coming.</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><category term="3D" /><summary type="html"><![CDATA[In this post I show you how to create a compressed ktx2 file from a set of cubemap images.]]></summary></entry><entry><title type="html">Reverse proxy external https backend with Caddy</title><link href="https://hals.app/blog/caddy-reverse-proxy/" rel="alternate" type="text/html" title="Reverse proxy external https backend with Caddy" /><published>2024-12-08T00:00:00+00:00</published><updated>2024-12-08T00:00:00+00:00</updated><id>https://hals.app/blog/caddy-reverse-proxy</id><content type="html" xml:base="https://hals.app/blog/caddy-reverse-proxy/"><![CDATA[<p>In this post, I’ll introduce you to Caddy and guide you through configuring it as a reverse proxy for hosting a Single Page App (SPA).</p>

<p>The post expects that you have a caddy server running with a basic Caddyfile, and will help you configure the SPA and https backend configuration.</p>

<h2 id="single-page-apps-and-backend-apis">Single Page Apps and Backend APIs</h2>

<p>Most Single Page Apps rely on a backend API, and sometimes that API is hosted on a different server. If your backend is secured, you might need to send Authorization headers, and the backend server will need to support Cross-Origin Resource Sharing (CORS) headers.</p>

<p>CORS requests involve an additional roundtrip to the server, which can double the request time and increase latency when using your app.</p>

<p>By setting up a reverse proxy, you can “hide” the fact that your backend is hosted on a separate server, improving performance and simplifying your architecture.</p>

<h2 id="a-common-setup">A Common Setup</h2>

<p>A typical reverse proxy setup might look like this:</p>

<p><code class="language-plaintext highlighter-rouge">/index.html</code>: This is retrieved directly from Server A 🟢.</p>

<p><code class="language-plaintext highlighter-rouge">/backend/api/posts</code>: This request is forwarded by Server A to Server B🔵’s <code class="language-plaintext highlighter-rouge">/api/posts</code> route. (Server A then sends the response back to the browser).</p>

<p>In this setup, the browser doesn’t know that it’s actually communicating with a backend hosted on another domain. The browser does not require CORS pre-requests, and its easier to secure your API and website. 😎</p>

<h2 id="what-is-caddy">What is Caddy?</h2>

<p>Caddy is a powerful web server designed to handle a variety of tasks, including serving as a reverse proxy. It’s simple to configure, flexible, and well-suited for modern web applications like SPAs. Caddy fills the same role as other servers such as <code class="language-plaintext highlighter-rouge">nginx</code> or <code class="language-plaintext highlighter-rouge">traefik</code>.</p>

<h2 id="setting-up-caddy-as-a-reverse-proxy-for-spa">Setting Up Caddy as a Reverse Proxy for SPA</h2>

<p>Here’s a basic Caddyfile to configure Caddy as a reverse proxy for an SPA:</p>

<pre><code class="language-caddy"># This file was tested with Caddy v2.9

# Basic reverse proxy setup
yourdomain.example.com {

  # IMPORTANT: `handle_path` automatically "replaces" the route when forwarding.
  # Example: /backend/api/blog/posts will be forwarded to /api/blog/posts.
  # If you don't want this replacement behavior, use `handle` instead.
  handle_path /backend/* {
    reverse_proxy {
      to your_backend_url:8000
      # If your backend is HTTPS, see the example below for additional configuration.
    }
  }

  # This is the basic SPA hosting setup
  handle {
    # Root: We assume the SPA files are located in the /srv directory.
    root * /srv
    # `try_files` will fall back to the index if no matching files are found.
    try_files {path} /index.html

    # file_server will serve static files from the root path.
    file_server
  }
}
</code></pre>

<h2 id="modifying-for-external-https-backends">Modifying for External HTTPS Backends</h2>

<p>If your reverse proxy is forwarding requests to an external server hosted on HTTPS, you’ll need to make a small modification to handle this correctly. Specifically, you need to adjust the Host header.</p>

<p>Here’s how to modify your Caddyfile for this scenario:</p>

<pre><code class="language-caddy">handle_path /backend/* {
  reverse_proxy {
    to https://your_backend_url.example.com
    # If the target server is external and hosted over HTTPS,
    # you need to modify the `Host` header to match the target server's host.
    header_up Host {http.reverse_proxy.upstream.hostport}
    # This ensures the correct Host header is sent for HTTPS requests.
  }
}
</code></pre>

<p>This configuration forwards requests to an external server securely, ensuring proper handling of the Host header for HTTPS connections.</p>

<p class="notice--info">Note: The guide was written with Caddy 2.9 in mind at the end of 2024, and may be outdated when you read this. Please check with the official docs if anything does not work as expected. <a href="https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas">https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>That’s all you need to set up a reverse proxy with Caddy to host an SPA! If you’re interested in learning how to run Caddy with Docker, stay tuned for a future post on that topic.</p>

<p>For more detailed documentation, visit the official Caddy docs: <a href="https://caddyserver.com/docs">https://caddyserver.com/docs</a></p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="caddy" /><summary type="html"><![CDATA[In this post, I’ll introduce you to Caddy and guide you through configuring it as a reverse proxy for hosting a Single Page App (SPA).]]></summary></entry><entry><title type="html">shift_space_for_snake_case using PowerToys</title><link href="https://hals.app/blog/underscore-shortcut/" rel="alternate" type="text/html" title="shift_space_for_snake_case using PowerToys" /><published>2024-08-30T00:00:00+00:00</published><updated>2024-08-30T00:00:00+00:00</updated><id>https://hals.app/blog/underscore-shortcut</id><content type="html" xml:base="https://hals.app/blog/underscore-shortcut/"><![CDATA[<p>My biggest problem with <code class="language-plaintext highlighter-rouge">snake_case</code> in programming is that I find it flow-breaking to write the underscore separator character.</p>

<p>If I had a dedicated underscore key on my keyboard it would be a lot easier to cope with.</p>

<p>There are some guides that suggest AutoHotKey remapping a key, but I did not have the opportunity to install AutoHotKey on my work computer.</p>

<p>But the Windows PowerToys package from Microsoft is allowed on my PC, and that has a built in Keyboard Manager.</p>

<p>In PowerToys you are able to remap Shortcuts:</p>

<figure class=""><img src="/assets/images/powertoys-key-remap.png" alt="Screenshot showing the remapping of shortcuts functionality in PowerToys" /><figcaption>
      Remapping shortcuts in PowerToys

    </figcaption></figure>

<p>By using the remap functionality you can easily add remaps for singular apps so they do not get in the way for shortcuts in other apps!</p>

<p>This way I added the shortcut <code class="language-plaintext highlighter-rouge">Shift-Space</code> to write <code class="language-plaintext highlighter-rouge">_</code> instead of doing nothing in Godot Engine! I just need one hand to write a underscore now. Finally I can write Python or GDScript without annoyances!</p>

<p>In the future I might consider remapping <code class="language-plaintext highlighter-rouge">CAPS_LOCK</code> as a dedicated underscore key, as that makes it just a single key-press for an underscore. Making it just as easy to write <code class="language-plaintext highlighter-rouge">snake_case</code> as <code class="language-plaintext highlighter-rouge">camelCase</code>!</p>

<p>Thats it for this time. Hope this helps someone out!</p>

<p>// Nils Henrik</p>]]></content><author><name>Nils Henrik Hals</name></author><category term="Blog" /><category term="shorts" /><summary type="html"><![CDATA[My biggest problem with snake_case in programming is that I find it flow-breaking to write the underscore separator character.]]></summary></entry></feed>