<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julio Casal</title>
    <description>The latest articles on DEV Community by Julio Casal (@juliocasal).</description>
    <link>https://dev.to/juliocasal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3404362%2Fa504c820-55c9-41d2-8b9b-207362bb4145.jpg</url>
      <title>DEV Community: Julio Casal</title>
      <link>https://dev.to/juliocasal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juliocasal"/>
    <language>en</language>
    <item>
      <title>Build a Reusable .NET Aspire API Template in Minutes</title>
      <dc:creator>Julio Casal</dc:creator>
      <pubDate>Sat, 09 Aug 2025 07:00:00 +0000</pubDate>
      <link>https://dev.to/juliocasal/build-a-reusable-net-aspire-api-template-in-minutes-2759</link>
      <guid>https://dev.to/juliocasal/build-a-reusable-net-aspire-api-template-in-minutes-2759</guid>
      <description>&lt;p&gt;A few months ago, I was trying to find a way to come up with some sort of template for future .NET API projects based on .NET Aspire.&lt;/p&gt;

&lt;p&gt;The standard Aspire templates give you a great start, but I had already finished another Aspire application with tons of essential building blocks that I did not want to manually bring to the new project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There had to be a better way than cloning and tweaking everything by hand.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Well, this is exactly what .NET project templates are designed for, and when taking full advantage of their features, they can really streamline the way you start new Aspire-based projects.&lt;/p&gt;

&lt;p&gt;Today, I’ll show you how I created my new .NET Aspire API Starter template, step-by-step.&lt;/p&gt;

&lt;p&gt;Let’s start.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The starting point&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The Aspire-enabled .NET API I had just finished already had most of the things I could reuse in future projects, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vertical slice architecture&lt;/li&gt;
&lt;li&gt;Classic (and not-so-classic) CRUD endpoints&lt;/li&gt;
&lt;li&gt;EF Core integration with PostgreSQL&lt;/li&gt;
&lt;li&gt;Global error handling&lt;/li&gt;
&lt;li&gt;Authorization policies&lt;/li&gt;
&lt;li&gt;JWT authentication with support for Entra ID&lt;/li&gt;
&lt;li&gt;Azure Storage integration&lt;/li&gt;
&lt;li&gt;An Aspire AppHost project with:

&lt;ul&gt;
&lt;li&gt;A complete application model&lt;/li&gt;
&lt;li&gt;Support for Azure services, ready to deploy&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;An Aspire Service Defaults project with:

&lt;ul&gt;
&lt;li&gt;Custom health checks&lt;/li&gt;
&lt;li&gt;Custom health checks&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;An integration tests project, with several working tests&lt;/li&gt;

&lt;li&gt;An Azure DevOps CI/CD pipeline&lt;/li&gt;

&lt;li&gt;A lot of other reusable stuff. &lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;At a high level, it looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqa0pga4lglfslb7o7z4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqa0pga4lglfslb7o7z4.jpeg" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;To turn this into a template, I started by generalizing things a bit.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Generalize project items&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You don’t have to do this, but it didn’t feel right to have &lt;em&gt;GameStore&lt;/em&gt; spread all over the soon-to-be template.&lt;/p&gt;

&lt;p&gt;Plus, that term will be used as a placeholder to be replaced with the actual name given to the project by the user.&lt;/p&gt;

&lt;p&gt;So I replaced all instances of GameStore with &lt;strong&gt;TemplateApp,&lt;/strong&gt; a more generic term:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzazunwyzh9fmt8qix0h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzazunwyzh9fmt8qix0h.jpeg" width="800" height="1113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Notice this applies not only to folders and project files, but also to every namespace and class influenced by the name of the project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbon1mub5pb1uzq8rb0ys.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbon1mub5pb1uzq8rb0ys.jpeg" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Even to the CI/CD pipeline yml contents:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ocr7lzhpdtnbhw925t6.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ocr7lzhpdtnbhw925t6.jpeg" width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;It’s not the most fun part of this process, but with the help of the Copilot Agent, it didn’t take long.&lt;/p&gt;

&lt;p&gt;Next: time to add the template configuration.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Add the template configuration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To turn the templatized application into an actual template, all you need to do is add a &lt;strong&gt;template.json&lt;/strong&gt; file in a new &lt;strong&gt;.template.config&lt;/strong&gt; folder at the root of the repo.&lt;/p&gt;

&lt;p&gt;Here’s my template.json file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle2sgr0pm0jap27tvyw2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle2sgr0pm0jap27tvyw2.jpeg" width="800" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Let’s go over the not-so-obvious key elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;classifications:&lt;/strong&gt; These will show in the &lt;em&gt;Tags&lt;/em&gt; column when your template appears in the &lt;em&gt;dotnet new list&lt;/em&gt; command results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shortName:&lt;/strong&gt; The name you will use with &lt;em&gt;dotnet new&lt;/em&gt; to create a project using your template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sourceName:&lt;/strong&gt; The name to be replaced across all template dirs and files with the value specified by the user via the &lt;em&gt;–name&lt;/em&gt; argument.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;preferNameDirectory:&lt;/strong&gt; Makes it so a directory is created for your project if &lt;em&gt;–name&lt;/em&gt; was specified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are tons more things you could configure in that file, but I found these to be a great start.&lt;/p&gt;

&lt;p&gt;Now, let’s install the template.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Install the template&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To use the template, you first have to install it. But, before doing that, make sure you delete any extra folders you don’t want to get included with it, like your &lt;em&gt;bin&lt;/em&gt; or &lt;em&gt;obj&lt;/em&gt; dirs.&lt;/p&gt;

&lt;p&gt;Then, open a terminal at the root of your template, and use the &lt;strong&gt;dotnet new install&lt;/strong&gt; command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1663co49qsvrqn6vebt4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1663co49qsvrqn6vebt4.jpeg" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;You should now be able to list your template with the familiar &lt;strong&gt;dotnet new list&lt;/strong&gt; command:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowcm4nwxe3s9j35t1fs0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fowcm4nwxe3s9j35t1fs0.jpeg" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Great, now let’s put it to the test.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Using the template&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let’s say I want to create a new Aspire-enabled API for my new inventory management application.&lt;/p&gt;

&lt;p&gt;Easy to get started with the new template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35d82gvl7du4k1tzpkea.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35d82gvl7du4k1tzpkea.jpeg" width="800" height="83"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Let’s open the new project in VS Code and confirm all projects and files look good:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1bhjkbffh058nr163r1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1bhjkbffh058nr163r1.jpeg" width="800" height="1038"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;What about classes?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9440pj5w2k00vkzr4584.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9440pj5w2k00vkzr4584.jpeg" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;But can I build and run this? Will it work?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqphxeiskefm1i5pcsbhq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqphxeiskefm1i5pcsbhq.jpeg" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Yep. Let’s also open the Aspire Dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfc47yqw6l8ya6ryb2fy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfc47yqw6l8ya6ryb2fy.jpeg" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Great!&lt;/p&gt;

&lt;p&gt;Finally, what about the integration tests?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhiqmejtu6kkrm1kk9cq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhiqmejtu6kkrm1kk9cq.jpeg" width="800" height="747"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Mission Accomplished!&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Wrapping Up&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;.NET project templates aren’t just about saving time—they’re about building consistency across your projects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually recreating the same Aspire setup, authentication flows, and project structure every time, you get a battle-tested foundation that just works.&lt;/p&gt;

&lt;p&gt;One template. Infinite projects. Zero repetitive setup.&lt;/p&gt;

&lt;p&gt;And that’s all for today.&lt;/p&gt;

&lt;p&gt;See you next Saturday.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; If you need it, the complete .NET Aspire API Starter template is now available &lt;a href="https://patreon.com/juliocasal" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Whenever you’re ready, there are 4 ways I can help you:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/waitlist" rel="noopener noreferrer"&gt;​Stripe for .NET Developers (Waitlist)​&lt;/a&gt;&lt;/strong&gt;: Add real payments to your .NET apps with Stripe—fast, secure, production-ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/courses/dotnetbootcamp" rel="noopener noreferrer"&gt;.NET Cloud Developer Bootcamp&lt;/a&gt;&lt;/strong&gt;: A complete blueprint for C# developers who need to build production-ready .NET applications for the Azure cloud.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;​&lt;a href="https://www.patreon.com/juliocasal" rel="noopener noreferrer"&gt;​Get the full source code&lt;/a&gt;&lt;/strong&gt;: Download the working project from this newsletter, grab exclusive course discounts, and join a private .NET community.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/sponsorship" rel="noopener noreferrer"&gt;Promote your business to 25,000+ developers&lt;/a&gt;&lt;/strong&gt; by sponsoring this newsletter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dotnet</category>
      <category>aspire</category>
    </item>
    <item>
      <title>Keycloak Tutorial for .NET Developers</title>
      <dc:creator>Julio Casal</dc:creator>
      <pubDate>Sat, 01 Feb 2025 08:00:00 +0000</pubDate>
      <link>https://dev.to/juliocasal/keycloak-tutorial-for-net-developers-1foi</link>
      <guid>https://dev.to/juliocasal/keycloak-tutorial-for-net-developers-1foi</guid>
      <description>&lt;p&gt;In one of my recent newsletters, I claimed OpenID Connect (OIDC) is the right way to configure authentication and authorization for your ASP.NET Core apps. No need to reinvent the wheel.&lt;/p&gt;

&lt;p&gt;However, ASP.NET Core does not include a built-in OIDC server, so you are on your own trying to figure out what to use out of dozens of possible free, paid, local, or hosted options.&lt;/p&gt;

&lt;p&gt;If you deploy your apps to Azure, a great option is Microsoft Entra ID, which I covered &lt;a href="https://juliocasal.com/blog/Securing-Aspnet-Core-Applications-With-OIDC-And-Microsoft-Entra-ID" rel="noopener noreferrer"&gt;here&lt;/a&gt;. But, for local development, I find that to be overkill.&lt;/p&gt;

&lt;p&gt;You should be able to run one command in your box and have everything needed to test your app with all the OIDC goodness without ever having to leave your box or pay for cloud services.&lt;/p&gt;

&lt;p&gt;So today I’ll show you how to secure your ASP.NET Core Apps with OIDC and Keycloak, from scratch.&lt;/p&gt;

&lt;p&gt;Let’s dive in.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is Keycloak?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Keycloak is an open-source identity and access management (IAM) tool that helps secure applications and services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6sl9lp7jp1h6f0kf6vjp.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6sl9lp7jp1h6f0kf6vjp.jpeg" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;I love to use it primarily for local development because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It’s easy to run it locally&lt;/strong&gt; using nothing more than Docker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It has no cloud dependencies&lt;/strong&gt; , making it fast and free from cloud-related costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It has a simple web admin UI&lt;/strong&gt; for managing users, roles, clients, scopes, and many other things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is free and open-source&lt;/strong&gt; , so you can go straight to the source if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is OpenID Connect certified&lt;/strong&gt; , so it must support all the OIDC goodness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Now let’s see how to configure it for local development.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Run Keycloak via Docker&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Make sure you have already installed and started &lt;strong&gt;Docker Desktop&lt;/strong&gt; in your box, and then create a &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file somewhere in your repo with these contents:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnhlr720wtm1fc0zwyew.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnhlr720wtm1fc0zwyew.jpeg" width="800" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;That configures the container to run Keycloak version &lt;strong&gt;26.1&lt;/strong&gt; , exposing its admin portal on &lt;strong&gt;port 8080&lt;/strong&gt; , using &lt;strong&gt;admin&lt;/strong&gt; for user and password, and with a &lt;strong&gt;volume&lt;/strong&gt; to not lose our settings when stopping the container.&lt;/p&gt;

&lt;p&gt;We also configure it to run in &lt;strong&gt;dev mode&lt;/strong&gt; (start-dev), which avoids a few restrictions that should be enforced in Production only.&lt;/p&gt;

&lt;p&gt;Open a terminal wherever you saved this file and run this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkfqwn2m7gysj75jcwz2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkfqwn2m7gysj75jcwz2.jpeg" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Now open your browser and navigate to this page:&lt;/p&gt;

&lt;p&gt;​&lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;​&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz6ijojhy0nmhgiqxhmc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flz6ijojhy0nmhgiqxhmc.jpeg" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Sign in with &lt;strong&gt;admin&lt;/strong&gt; for user and password and you’ll land on Keycloak’s home page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flthtrz37wqijbayrufgd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flthtrz37wqijbayrufgd.jpeg" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Now let’s configure the realm.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Create the realm&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A Keycloak Realm is a logical space for managing users, roles, groups, and authentication configurations within a Keycloak instance.&lt;/p&gt;

&lt;p&gt;You can create one by clicking on &lt;strong&gt;Create realm&lt;/strong&gt; in the realm drop-down:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzdotdtj9869ruccx5fw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxzdotdtj9869ruccx5fw.jpeg" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;In the next screen enter a realm name that matches what your app is about and hit &lt;strong&gt;Create:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2ixb55fqxor457n1cne.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2ixb55fqxor457n1cne.jpeg" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;The realm for our Game Store app is ready. Next, let’s add a user.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Add a user&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;User management is very straightforward in Keycloak. Just head to the &lt;strong&gt;Users&lt;/strong&gt; section and click &lt;strong&gt;Create new user&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vdrmr5slzyuqnjk4f9n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vdrmr5slzyuqnjk4f9n.jpeg" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;In the next screen, enter your user details, turn on &lt;em&gt;Emal verified&lt;/em&gt; (to keep things simple), and hit &lt;strong&gt;Create&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yzj58kd592503vioouo.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yzj58kd592503vioouo.jpeg" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Here you also want to set a password for your user, so next click on the &lt;strong&gt;Credentials&lt;/strong&gt; tab and click on &lt;strong&gt;Set password&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8t3btiofba1t2hcf34k.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8t3btiofba1t2hcf34k.jpeg" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Any password will do, since this is for local dev. Also, no need to make it temporary.&lt;/p&gt;

&lt;p&gt;Next, let’s register our first client.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Register the back-end&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The access tokens generated by Keycloak to authorize requests to our back-end API must include the correct audience. That’s how our back-end can confirm the token is intended for it.&lt;/p&gt;

&lt;p&gt;And, for that, we’ll need to register our back-end as a client in Keycloak, which you can do by going to the &lt;strong&gt;Client&lt;/strong&gt; tab and then clicking on &lt;strong&gt;Create client&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwkcv3z0r0zzfryxik1g.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwkcv3z0r0zzfryxik1g.jpeg" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;On the &lt;strong&gt;General settings&lt;/strong&gt; wizard step, enter a meaningful ID for this client. I’ll use &lt;em&gt;gamestore-api&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqg4dbq61516li2uqd6j.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwqg4dbq61516li2uqd6j.jpeg" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Step 2 of the wizard is meant for clients that start an OIDC authorization flow, like our front-end, but it does nothing for our backend, so you can keep everything OFF:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu64j325b4yu8myaanhs.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu64j325b4yu8myaanhs.jpeg" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;There’s nothing to enter in the last wizard step, so just hit &lt;strong&gt;Save&lt;/strong&gt; and your client is created.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 5: Configure the audience&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We need to create the client scope that, if granted, will attach the correct audience to the access tokens.&lt;/p&gt;

&lt;p&gt;So, go to &lt;strong&gt;Client scopes&lt;/strong&gt; and then click on &lt;strong&gt;Create client scope&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figyn6sx41g2p3nzjldp4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figyn6sx41g2p3nzjldp4.jpeg" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;For the client scope &lt;strong&gt;name&lt;/strong&gt; you should enter a value that reflects the kind of permissions that clients will be granted if they are allowed to request that scope.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1ej2pjuwxt2i153lyyy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1ej2pjuwxt2i153lyyy.jpeg" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;I used &lt;em&gt;gamestore_api.all&lt;/em&gt; since this scope grants access to the entire back-end API. This can be further enforced in your back-end authorization policies.&lt;/p&gt;

&lt;p&gt;Turn on &lt;strong&gt;Include in token scope&lt;/strong&gt; so that the scope is included in the generated access token (if you need it) and hit &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, we have to configure this scope so that it will produce the right audience in the token when requested. For that, while in this same client scope details screen, click on &lt;strong&gt;Mappers&lt;/strong&gt; and &lt;strong&gt;Configure a new mapper&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybe2cnran7mz3tbjdic4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybe2cnran7mz3tbjdic4.jpeg" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;From the list select &lt;strong&gt;Audience&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ugbvjcoobvaomdy8al2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ugbvjcoobvaomdy8al2.jpeg" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;You can enter any name for this audience, it’s not very relevant, but the key detail is to ensure you select your API client for &lt;strong&gt;Included Client Audience:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8xlzlqktto9r61ve7v1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8xlzlqktto9r61ve7v1.jpeg" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;That will ensure any time the &lt;em&gt;gamestore_api.all&lt;/em&gt; scope is requested and granted, the &lt;em&gt;gamestore-api&lt;/em&gt; audience will be added to the access token.&lt;/p&gt;

&lt;p&gt;There’s nothing else to do on this screen, so just hit &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now let’s register our second client.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 6: Register the front-end&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Since we’ll be using the OIDC authorization code flow, which starts from the front-end, we need to register our front-end as a client in Keycloak.&lt;/p&gt;

&lt;p&gt;For this, go back to the &lt;strong&gt;Clients&lt;/strong&gt; section and click on &lt;strong&gt;Create client&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftme9sn8z6f9m1bxa6quy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftme9sn8z6f9m1bxa6quy.jpeg" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;General settings&lt;/strong&gt; screen use a meaningful ID for your front-end, which you’ll use later in your app code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0ija4lm91a0epmetnug.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0ija4lm91a0epmetnug.jpeg" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Capability config&lt;/strong&gt; step, the &lt;strong&gt;Client authentication&lt;/strong&gt; check goes On or Off depending on your type of client:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public client -&amp;gt; On:&lt;/strong&gt; This is for SPA-style clients like React, Angular, Blazor WASM, or for mobile apps, since they can’t safely store secrets (anything that runs in the user’s device can be hacked).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private client -&amp;gt; Off:&lt;/strong&gt; This is for server-side apps, like Blazor server or old-style ASP.NET MVC or Razor apps. Those can safely store secrets on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqcccxj7t9w4884bwty6.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqcccxj7t9w4884bwty6.jpeg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;In my case, my front-end is a Blazor app with Static Server Side Rendering, no client interactivity, so I’ll turn Client authentication On.&lt;/p&gt;

&lt;p&gt;For Authentication flow, the &lt;strong&gt;Standard flow&lt;/strong&gt; is all you need.&lt;/p&gt;

&lt;p&gt;Moving on to the &lt;strong&gt;Login settings&lt;/strong&gt; , you want to enter both the &lt;strong&gt;Valid redirect URIs&lt;/strong&gt; and &lt;strong&gt;Valid post logout redirect URIs&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbpsb8sfr3j96z5kdecx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbpsb8sfr3j96z5kdecx.jpeg" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;host:port&lt;/em&gt; to use there depends on the host and port where you run your front-end locally, &lt;em&gt;&lt;a href="http://localhost:5002" rel="noopener noreferrer"&gt;http://localhost:5002&lt;/a&gt;&lt;/em&gt; in my case.&lt;/p&gt;

&lt;p&gt;Then, to keep it simple, I used the default paths used by ASP.NET Core’s OIDC middleware (&lt;em&gt;signin-oidc&lt;/em&gt;, &lt;em&gt;signout-callback-oidc&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;You can use any other paths there, but you have to make sure they exist in your front-end and that the OIDC middleware is aware of them.&lt;/p&gt;

&lt;p&gt;Hit &lt;strong&gt;Save&lt;/strong&gt; and your front-end is &lt;em&gt;mostly&lt;/em&gt; good to go.&lt;/p&gt;

&lt;p&gt;The missing piece is allowing our front-end to request the client scope we created earlier. That’s how it can get access tokens with the correct audience.&lt;/p&gt;

&lt;p&gt;So from your front-end screen go to &lt;strong&gt;Client scopes&lt;/strong&gt; and &lt;strong&gt;click on Add client scope&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4hw46tsgly6axnl8b3p.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4hw46tsgly6axnl8b3p.jpeg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Select the scope you had created (&lt;em&gt;gamestore_api.all&lt;/em&gt; in my case), click &lt;strong&gt;Add,&lt;/strong&gt; and choose Default or Optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw0zthe5zqmqgmbbepfe.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjw0zthe5zqmqgmbbepfe.jpeg" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Optional means the client must request the scope, while Default means the scope is always requested for this client, even if it doesn’t ask for it.&lt;/p&gt;

&lt;p&gt;I like to pick Optional here to let the client be very explicit on what it requests.&lt;/p&gt;

&lt;p&gt;That’s all the required Keycloak configuration. Now let’s see what to do to wire things up on the code side.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 7: ASP.NET Core back-end configuration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We need to tell our back-end 2 key things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who will be issuing the access tokens (Authority)&lt;/li&gt;
&lt;li&gt;Who are those tokens meant for (Audience)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can achieve those by first installing the &lt;strong&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/strong&gt; NuGet package and then registering a JWT Bearer scheme with the right values on our application startup code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3wkdwm6utseg0risjom.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3wkdwm6utseg0risjom.jpeg" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Notice that &lt;strong&gt;Authority&lt;/strong&gt; is the &lt;em&gt;host:port&lt;/em&gt; where Keycloak is running plus &lt;em&gt;/realms/your-realm-name&lt;/em&gt;, and &lt;strong&gt;Audience&lt;/strong&gt; must match exactly the audience we configured earlier on Keycloak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RequireHttpsMetadata&lt;/strong&gt; needs to be false so that the middleware can talk to our Keycloak server locally, which is not using HTTPS.&lt;/p&gt;

&lt;p&gt;Also, add this line to turn on the Authorization middleware:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq22r6nqia1t5cww4u2kb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq22r6nqia1t5cww4u2kb.jpeg" width="800" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;And then update your endpoints to require authorization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxv5jdkeq7ac34yri5z9.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxv5jdkeq7ac34yri5z9.jpeg" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;The back-end should be ready. Now, let’s see what to do on the front-end.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 8: Front-end configuration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;How you configure the front-end for OIDC will vary wildly depending on your tech stack, but here’s what you can do for a Blazor Static SSR app.&lt;/p&gt;

&lt;p&gt;First install the &lt;strong&gt;Microsoft.AspNetCore.Authentication.OpenIdConnect&lt;/strong&gt; NuGet package, which gives you access to the OIDC middleware.&lt;/p&gt;

&lt;p&gt;Then, configure your Keycloak scheme during your application startup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmdukzarn1wtyep2fw6c.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmdukzarn1wtyep2fw6c.jpeg" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;You can get your &lt;strong&gt;ClientId&lt;/strong&gt; and &lt;strong&gt;ClientSecret&lt;/strong&gt; from your front-end client configuration on Keycloak:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ydi2ue8901gmuzqnzy0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ydi2ue8901gmuzqnzy0.jpeg" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Just don’t hard code the secret there, and instead load it from .NET’s Secret Manager. Also, keep in mind that a client secret is not required for public clients since they can’t hold secrets.&lt;/p&gt;

&lt;p&gt;Regarding the other values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authority:&lt;/strong&gt; The path to your realm in your Keycloak server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; The exact scope we defined earlier and that grants the correct audience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ResponseType:&lt;/strong&gt; The flow to use to authenticate the user. &lt;strong&gt;Code&lt;/strong&gt; corresponds to the &lt;strong&gt;Authorization Code Flow&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaveTokens:&lt;/strong&gt; True so that received tokens can be saved in the authentication cookie to be used later when we send requests to the backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SignInScheme:&lt;/strong&gt; The scheme used to sign in the user. We use cookies here so the user can remain authenticated between requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SignOutScheme:&lt;/strong&gt; The scheme used to sign out the user. We use Keycloak here so that the user is signed out of both Keycloak and the front-end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RequireHttpsMetadata:&lt;/strong&gt; Needs to be false, same reason as the back-end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TokenValidationParameters.NameClaimType:&lt;/strong&gt; Setting this to &lt;strong&gt;JwtRegisteredClaimNames.Name&lt;/strong&gt; will make sure the user’s name is populated in the claims principal after the user authenticates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MapInboundClaims:&lt;/strong&gt; Prevents ASP.NET Core from changing the name of a few key claims. We want the exact claims coming from Keycloak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;You will also need to add the Cookies auth scheme, so that it can be used alongside the OIDC middleware:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom12spu1hjfuyy23re89.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fom12spu1hjfuyy23re89.jpeg" width="800" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;How will Blazor components know if the user is authenticated or not?&lt;/p&gt;

&lt;p&gt;By using the authorization middleware, an authentication state provider, and the cascading authentication state, which you can register like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd201pqek2bby9udoeiux.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd201pqek2bby9udoeiux.jpeg" width="800" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;That’s how we can do stuff like this elsewhere:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhjuouzmcpx5py1z901a.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhjuouzmcpx5py1z901a.jpeg" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;We should also add an endpoint in the front-end to kick off the OIDC sign-in flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmunogmaqhblhbmn2qnh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmunogmaqhblhbmn2qnh.jpeg" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;That endpoint will be invoked by your Login button or link. Something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ordxnsyvpoa58hrg8s5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ordxnsyvpoa58hrg8s5.jpeg" width="800" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;This should be enough for your front-end to authenticate users via Keycloak.&lt;/p&gt;

&lt;p&gt;But, there’s one more thing needed to let the front-end make authenticated requests to the backend.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 9: Add an authorization handler&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Just because the user is authenticated in the front-end doesn’t mean the back-end will automatically trust the front-end to make requests on behalf of the user.&lt;/p&gt;

&lt;p&gt;We need to make sure we attach the &lt;strong&gt;access token&lt;/strong&gt; as a header on every request we make to the back-end API endpoints that require authorization.&lt;/p&gt;

&lt;p&gt;We could do this manually in every request, but it’s easier via a delegating handler like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep847udscmluook8kgvo.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep847udscmluook8kgvo.jpeg" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;As you can see, all we do there is get the access token from the current HTTP context and attach it as a &lt;strong&gt;Bearer&lt;/strong&gt; token in the &lt;strong&gt;Authorization&lt;/strong&gt; header of the request.&lt;/p&gt;

&lt;p&gt;Finally, to enable our authorization hander, we need to register it and add it to our typed HTTP client in the front-end:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8j9iozcqzoflv80cfah.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8j9iozcqzoflv80cfah.jpeg" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 10: ASP.NET Core + Keycloak in action&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now run your backend and frontend applications and navigate to the frontend in your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo12q4khtewkyhk7zs8kc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo12q4khtewkyhk7zs8kc.jpeg" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Now click on the &lt;strong&gt;Login&lt;/strong&gt; button and you’ll land on Keycloak’s login page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffe3u13s2oqvyt47dfa9d.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffe3u13s2oqvyt47dfa9d.jpeg" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;And, after login, you’ll get redirected to the home page, but with a few differences:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqun5bu310e0zh0yor7z7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqun5bu310e0zh0yor7z7.jpeg" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;You can now see UI elements only meant for authorized users, like the &lt;em&gt;Catalog&lt;/em&gt; link there, plus you can see details about the authenticated user, like the name and a small gravatar generated out of the user email.&lt;/p&gt;

&lt;p&gt;You can also have the front-end make use of protected back-end APIs that require the presence of an access token.&lt;/p&gt;

&lt;p&gt;For instance, here’s the shopping cart page, where the back-end must know who the current user is and what kind of access has been granted to return the correct cart:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd1g77ty8dakm5ogiync.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdd1g77ty8dakm5ogiync.jpeg" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;So there you go, ASP.NET Core + Keycloak working end to end, all secured with the industry standard OIDC protocol.&lt;/p&gt;

&lt;p&gt;Mission accomplished! ​&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Wrapping up&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You now have a complete ASP.NET Core application secured with industry-standard OIDC authentication, all running locally without any cloud dependencies.&lt;/p&gt;

&lt;p&gt;Your backend API validates JWT tokens properly. Your frontend handles the authorization code flow seamlessly. And Keycloak provides enterprise-grade identity management that just works.&lt;/p&gt;

&lt;p&gt;This setup gives you everything you need for local development: a real identity provider, proper token validation, and the confidence that your authentication flow will work the same way in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to take this even further?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://juliocasal.com/blog/keycloak-tutorial-part-2-clone-and-f5-ready-with-net-aspire" rel="noopener noreferrer"&gt;Part 2 of this series&lt;/a&gt;, I’ll show you how to make this entire setup even more developer-friendly using .NET Aspire. We’ll eliminate the manual Keycloak configuration and make everything start automatically when you hit F5.&lt;/p&gt;

&lt;p&gt;Just clone, run, and authenticate. Every single time.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S.&lt;/strong&gt; For a step-by-step video guide to everything I showed you above plus advanced topics like role, claims and resource based authorization, and complete frontend integration with both Blazor and React applications, check out my &lt;a href="https://juliocasal.com/courses/aspnet-core-security" rel="noopener noreferrer"&gt;&lt;strong&gt;ASP.NET Core Security Course&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Whenever you’re ready, there are 4 ways I can help you:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://go.dotnetacademy.io/stripe-waitlist" rel="noopener noreferrer"&gt;​Stripe for .NET Developers (Waitlist)​&lt;/a&gt;&lt;/strong&gt;: Add real payments to your .NET apps with Stripe—fast, secure, production-ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/courses/containers-and-dotnet-aspire" rel="noopener noreferrer"&gt;Containers &amp;amp; .NET Aspire&lt;/a&gt;&lt;/strong&gt;: Build production-ready apps from day 1 and leave ‘but it works on my machine’ behind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;​&lt;a href="https://www.patreon.com/juliocasal" rel="noopener noreferrer"&gt;​Get the full source code&lt;/a&gt;&lt;/strong&gt;: Download the working project from this newsletter, grab exclusive course discounts, and join a private .NET community.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/sponsorship" rel="noopener noreferrer"&gt;Promote your business to 25,000+ developers&lt;/a&gt;&lt;/strong&gt; by sponsoring this newsletter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dotnet</category>
      <category>keycloak</category>
    </item>
    <item>
      <title>Understanding JSON Web Tokens</title>
      <dc:creator>Julio Casal</dc:creator>
      <pubDate>Sat, 04 Jan 2025 08:00:00 +0000</pubDate>
      <link>https://dev.to/juliocasal/understanding-json-web-tokens-39cg</link>
      <guid>https://dev.to/juliocasal/understanding-json-web-tokens-39cg</guid>
      <description>&lt;p&gt;Well, 2024 is gone, and wow it was such an amazing year in many aspects, but especially for software developers and AI enthusiasts.&lt;/p&gt;

&lt;p&gt;As I look back, here are a few 2024 breakthroughs that I think set the stage for how we will be doing things moving forward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPT-4o and Claude 3.5 Sonnet enabled more natural and versatile human-AI interactions, significantly changing the way we solve our everyday human problems.&lt;/li&gt;
&lt;li&gt;AI coding assistants like GitHub Copilot and Cursor are now integrated deeply into our developer workflows, reducing mundane tasks and allowing more focus on creative problem-solving.&lt;/li&gt;
&lt;li&gt;NVIDIA’s market value went beyond $3 trillion, reflecting how tech giants like Microsoft, Google, and Amazon can no longer live without their powerful AI chips.&lt;/li&gt;
&lt;li&gt;Microsoft’s 365 Copilot, their Azure cloud, and all their developer tools are now heavily powered by a myriad of OpenAI services, reshaping human productivity and innovation at all levels.&lt;/li&gt;
&lt;li&gt;.NET Aspire achieved not 1 but 2 major releases, reflecting the increasing demand for better tooling for cloud-native development with a strong focus on Azure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2025 is only going to get better, but boy, things are moving so fast!&lt;/p&gt;

&lt;p&gt;Today I want to dive into JWTs (JSON Web Tokens), and the key role they play in today’s web app security infrastructure.&lt;/p&gt;

&lt;p&gt;Let’s dive in.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is a JSON Web Token (JWT)?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Think of a JWT (pronounced “jot”) like a digital VIP pass at a concert. Just as a VIP pass contains information about who you are and what areas you can access, a JWT contains claims about a user and their permissions.&lt;/p&gt;

&lt;p&gt;The key difference is that a JWT is cryptographically signed, making it tamper-proof.&lt;/p&gt;

&lt;p&gt;To be more specific:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, before looking at real JWTs, I think it’s good to understand the concept of a claim.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is a claim?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In our VIP pass, we have several pieces of information about the pass holder:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lzxvi9pbkpfmj6m0lbb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lzxvi9pbkpfmj6m0lbb.jpeg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each of these pieces of information is like a “claim” in JWT terms.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A claim is a statement about an entity (typically, the user) and additional data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Think about it this way: When Thomas shows his VIP pass to a security guard, that pass is making several claims on his behalf:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The name claim&lt;/strong&gt; : “I am Thomas A. Anderson”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The access level claims&lt;/strong&gt; : “I have permission to enter the backstage area,” “I can enter the green room,” “I can attend the soundcheck”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The time validity claim&lt;/strong&gt; : “My privileges are valid until December 31, 2025”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a JWT, claims work the same way. When your application presents a JWT to a server, it’s like Thomas showing his VIP pass, which makes specific claims about Thomas and his granted access.&lt;/p&gt;

&lt;p&gt;But what do these JWTs look like?&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What’s in a JWT?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;JWTs follow a 3 part structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs7plaga6ym4eayomqf0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffs7plaga6ym4eayomqf0.jpeg" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;So there we got:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Header&lt;/strong&gt; , which contains information about the type of token and how it was signed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Payload&lt;/strong&gt; , which contains all the transmitted claims &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Signature&lt;/strong&gt; , _**_which is calculated from the encoded header and payload, an added secret, and the hashing algorithm specified in the header. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if you decode the payload of a JWT, as your Web app will do, you will get something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2w19j88eui4wjw10pzw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2w19j88eui4wjw10pzw.jpeg" width="800" height="788"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;So it’s just a list of claims related to Mr. Anderson and the access given to him in the system.&lt;/p&gt;

&lt;p&gt;To explain a few important ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;sub:&lt;/strong&gt; The unique ID of Thomas in the system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iss:&lt;/strong&gt; The URL of the authorization server that issued the token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;scope:&lt;/strong&gt; The type of access granted to the app, on behalf of Thomas, to use our backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;role:&lt;/strong&gt; The business role assigned to Thomas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;email:&lt;/strong&gt; Thomas’s registered email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But how are these JWTs used in real life and who creates them?&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Token-based Authentication&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let’s say you have a backend API that gates access to everything related to the concert venues and that Thomas wants to securely check all that info from his phone.&lt;/p&gt;

&lt;p&gt;Here is where we would use what is known as token-based authentication:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Token-based authentication&lt;/strong&gt; is a security mechanism in which a client authenticates itself to a server by presenting a unique token, which serves as a temporary credential to access protected resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It works like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fue70x0gnoxtmiy0uxwzk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fue70x0gnoxtmiy0uxwzk.jpeg" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Thomas Requests Authorization:&lt;/strong&gt; Thomas logs in through the mobile app to request access to concert details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization:&lt;/strong&gt; The authorization server checks Thomas’s credentials and approves his login.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT Issued:&lt;/strong&gt; The authorization server issues a JWT with all the claims that confirm the kind of access Thomas has been granted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Request:&lt;/strong&gt; The mobile app sends a request to the Concert Gate API attaching the issued JWT as a header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Validates JWT:&lt;/strong&gt; The API verifies the JWT for validity, expiration, and permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Responds:&lt;/strong&gt; The API returns the requested concert details to the mobile app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;Now, how do you deal with these JWTs in your ASP.NET Core APIs? Let’s tackle that in next week’s newsletter.&lt;/p&gt;

&lt;p&gt;And, if you need to learn how to configure Keycloak, a popular open-source authorization server, to authenticate your users and generate JWTs, I go over all those details (and lots more) in &lt;a href="https://juliocasal.com/courses/dotnetbootcamp" rel="noopener noreferrer"&gt;the bootcamp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;New ASP.NET Core Security course: recording complete!&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Earlier this week I finished recording course 3 of the bootcamp: &lt;strong&gt;ASP.NET Core Security&lt;/strong&gt; and, as expected, it ended up being the largest course in the bootcamp yet.&lt;/p&gt;

&lt;p&gt;This course is a bit longer to make sure you are ready to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How does ASP.NET Core validate and extract info from the JWTs attached to your requests?&lt;/li&gt;
&lt;li&gt;How do OAuth 2.0 and OpenID Connect (OIDC) work?&lt;/li&gt;
&lt;li&gt;How to read and transform claims?&lt;/li&gt;
&lt;li&gt;What are and how to use different authentication schemes?&lt;/li&gt;
&lt;li&gt;How to implement different types of authorization policies based on JWT claims?&lt;/li&gt;
&lt;li&gt;How can a full-stack application integrate with Keycloak to offload user login and registration and enable OIDC?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And tons of other stuff, including a mini-course on Docker for students new to that popular tech.&lt;/p&gt;

&lt;p&gt;I’m now going through all the post-production work, which ended up being a bit more than anticipated, but if all goes well this course should be ready for all bootcamp students by January 14.&lt;/p&gt;

&lt;p&gt;Now, back to work.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

&lt;p&gt;Julio&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Whenever you’re ready, there are 4 ways I can help you:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/waitlist" rel="noopener noreferrer"&gt;​Stripe for .NET Developers (Waitlist)​&lt;/a&gt;&lt;/strong&gt;: Add real payments to your .NET apps with Stripe—fast, secure, production-ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/courses/containers-and-dotnet-aspire" rel="noopener noreferrer"&gt;Containers &amp;amp; .NET Aspire&lt;/a&gt;&lt;/strong&gt;: Build production-ready apps from day 1 and leave ‘but it works on my machine’ behind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;​&lt;a href="https://www.patreon.com/juliocasal" rel="noopener noreferrer"&gt;​Get the full source code&lt;/a&gt;&lt;/strong&gt;: Download the working project from this newsletter, grab exclusive course discounts, and join a private .NET community.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/sponsorship" rel="noopener noreferrer"&gt;Promote your business to 25,000+ developers&lt;/a&gt;&lt;/strong&gt; by sponsoring this newsletter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Securing ASP.NET Core Apps With OIDC and Microsoft Entra ID</title>
      <dc:creator>Julio Casal</dc:creator>
      <pubDate>Sat, 25 May 2024 07:00:00 +0000</pubDate>
      <link>https://dev.to/juliocasal/securing-aspnet-core-apps-with-oidc-and-microsoft-entra-id-g3o</link>
      <guid>https://dev.to/juliocasal/securing-aspnet-core-apps-with-oidc-and-microsoft-entra-id-g3o</guid>
      <description>&lt;p&gt;Today I’ll show you how to secure your ASP.NET Core applications using OpenID Connect (OIDC) and Microsoft Entra ID.&lt;/p&gt;

&lt;p&gt;OIDC is the industry-standard protocol for authentication and is pretty much the go-to choice for securing full-stack applications these days.&lt;/p&gt;

&lt;p&gt;But the sad thing is that there are few things more frustrating than trying to understand and implement OIDC for the first time.&lt;/p&gt;

&lt;p&gt;However, it’s not rocket science and once you get the hang of it, you’ll be able to secure your applications with confidence.&lt;/p&gt;

&lt;p&gt;Let’s dive in.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is OpenID Connect?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Any &lt;a href="https://openid.net/developers/how-connect-works" rel="noopener noreferrer"&gt;OpenID Connect (OIDC)&lt;/a&gt; definition involves &lt;a href="https://oauth.net/2" rel="noopener noreferrer"&gt;OAuth 2.0&lt;/a&gt;, so you should first know that &lt;strong&gt;OAuth 2.0&lt;/strong&gt; is the industry-standard protocol for authorization, allowing a website or application to access resources hosted by other web apps on behalf of a user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OIDC&lt;/strong&gt; is nothing more than an authentication protocol built on top of OAuth 2.0 that allows clients to verify the identity of the end-user based on the authentication performed by an authorization server.&lt;/p&gt;

&lt;p&gt;OIDC supports several flows to authenticate users depending on your scenario, but one of the most modern and secure flows you can use is the &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce" rel="noopener noreferrer"&gt;Authorization Code Flow with PKCE&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Here’s how the OIDC &lt;strong&gt;Authorization Code Flow with PKCE&lt;/strong&gt; works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;end-user initiates an authorization request to perform some action in the resource server&lt;/strong&gt; (say, query resources from an ASP.NET Core API) via a client, also known as the relying party.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;client generates a code verifier&lt;/strong&gt; , which is a hard-to-guess value that will be used later in the flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;client then generates a code challenge&lt;/strong&gt; , which is an encoded hash value derived from the code verifier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;client sends the code challenge, along with the openid scope, to the authorization server&lt;/strong&gt; , also known as the OpenID provider. The openid scope indicates that the client wants to authenticate the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;OpenID provider presents the sign-in page&lt;/strong&gt; where the end-user can authenticate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After authentication, the OpenID provider might optionally also present an additional page where the end-user can provide explicit consent to which actions can be performed on the resources he owns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After this, the &lt;strong&gt;OpenID provider generates an authorization code&lt;/strong&gt; which it sends back to the client. It also stores the code challenge for future verification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now, the &lt;strong&gt;client can request an authorization token&lt;/strong&gt; by using the received authorization code plus the code verifier it had created at the start of the flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;OpenID provider verifies the authorization token request&lt;/strong&gt; by generating the code challenge itself out of the received code verifier and comparing it with the code challenge it had already stored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If all looks good, the &lt;strong&gt;OpenID provider generates two tokens: an access token and an ID token&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;client can verify the identity&lt;/strong&gt; of the end-user &lt;strong&gt;via the ID token&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;client can send requests to the resource server&lt;/strong&gt; to query for the resources it needs &lt;strong&gt;using the access token&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;resource server validates the received access token&lt;/strong&gt; and if all looks good it executes the requested action and returns the corresponding response.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I love this flow because of 3 key reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Security:&lt;/strong&gt; Prevents authorization code interception attacks by binding the authorization code to a code verifier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Defense in Depth:&lt;/strong&gt; Adds an additional layer of security beyond the client secret, making the overall authentication process more robust.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compliance with Best Practices:&lt;/strong&gt; Aligns with modern security standards and recommendations for OAuth 2.0 and OpenID Connect, ensuring a more secure implementation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Choosing an OpenID Provider&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There are several OpenID providers you can use to enable OIDC in your ASP.NET Core applications.&lt;/p&gt;

&lt;p&gt;A few of the popular ones I have used in the past are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://auth0.com" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://duendesoftware.com/products/identityserver" rel="noopener noreferrer"&gt;Duende IdentityServer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/fundamentals/whatis" rel="noopener noreferrer"&gt;Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.keycloak.org" rel="noopener noreferrer"&gt;Keycloak&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many others, but the key thing is that they all implement OIDC, which means that they can all enable the OIDC flow I described above.&lt;/p&gt;

&lt;p&gt;Plus, once you understand OIDC via one of them, you can easily switch between providers if needed.&lt;/p&gt;

&lt;p&gt;The choice of provider will depend on your specific requirements and constraints, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much you are willing to pay&lt;/li&gt;
&lt;li&gt;How much control you want over the authentication process&lt;/li&gt;
&lt;li&gt;How much you want to rely on third-party services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this post, I’ll focus on &lt;strong&gt;Microsoft Entra ID&lt;/strong&gt; because it’s part of the Microsoft ecosystem, and it’s a great starting point to understand how OIDC works.&lt;/p&gt;

&lt;p&gt;Prefer to watch instead? Here’s a full walkthrough:&lt;/p&gt;

&lt;p&gt;Let’s see how to secure an ASP.NET Core full-stack application via Microsoft Entra ID in a few simple steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Register your application in Microsoft Entra ID&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Our full-stack application is made of an ASP.NET Core API backend and a Blazor frontend.&lt;/p&gt;

&lt;p&gt;In this step we need to register the Blazor frontend in Microsoft Entra ID, which you can do by going to the &lt;strong&gt;App registrations&lt;/strong&gt; section of &lt;strong&gt;Microsoft Entra ID&lt;/strong&gt; on your &lt;a href="https://portal.azure.com/" rel="noopener noreferrer"&gt;Azure Portal&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Here I’ll go for a &lt;strong&gt;single-tenant application&lt;/strong&gt; to keep things simple, will select &lt;strong&gt;Web&lt;/strong&gt; as the platform (since it’s a server-side web application) and add the &lt;strong&gt;redirect URI&lt;/strong&gt; for my Blazor frontend.&lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;platform&lt;/strong&gt; you choose determines what kind of information Entra ID will demand from your app when configuring it for OIDC. For instance, it will not require a client secret for an SPA, but it is required for a Web app.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;redirect URI&lt;/strong&gt; is the location where Entra ID will send the authorization code after the user authenticates. In this case, it’s just the local URL of my Blazor frontend plus &lt;strong&gt;/signin-oidc&lt;/strong&gt; , the default path where the ASP.NET Core OIDC middleware will listen for the authorization code.&lt;/p&gt;

&lt;p&gt;After registering the app, you should go to &lt;strong&gt;Expose an API&lt;/strong&gt; and add the &lt;strong&gt;scope(s)&lt;/strong&gt; that your API backend supports (if any).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Scopes&lt;/strong&gt; are a way to define what actions the client can perform on the resources it requests. In this case, the scope &lt;strong&gt;api://8814267c-25fc-459e-b0a6-f6d7ed056f12/games:all&lt;/strong&gt; allows the client to perform any operation on the games managed by the API backend.&lt;/p&gt;

&lt;p&gt;The actual scope is &lt;strong&gt;games:all&lt;/strong&gt; , but Entra ID will automatically prepend it with the &lt;strong&gt;Client ID&lt;/strong&gt; assigned to your application.&lt;/p&gt;

&lt;p&gt;Lastly, you’ll need to head to &lt;strong&gt;Certificates &amp;amp; secrets&lt;/strong&gt; and create a new &lt;strong&gt;client secret&lt;/strong&gt; that you’ll use in your Blazor frontend as part of the OIDC flow.&lt;/p&gt;

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

&lt;p&gt;You’ll need more configuration there if you need to do things like support application roles, but this is good to get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: (Optional) Understand your access tokens&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You don’t have to do this, but I find it helpful to understand the access tokens that Entra ID will generate, so I can tell which claims to expect to find in them.&lt;/p&gt;

&lt;p&gt;To manually get an access token, you will first need to know your &lt;strong&gt;Authorization Endpoint&lt;/strong&gt; and your &lt;strong&gt;Token Endpoint&lt;/strong&gt;. Get those from the &lt;strong&gt;Overview&lt;/strong&gt; blade of your Entra ID app registration in the Portal:&lt;/p&gt;

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

&lt;p&gt;Then open up &lt;a href="https://www.postman.com/downloads" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;, create a new request, and head to the &lt;strong&gt;Authorization&lt;/strong&gt; tab:&lt;/p&gt;

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

&lt;p&gt;On the right side, under &lt;strong&gt;Configure New Token&lt;/strong&gt; , select &lt;strong&gt;Authorization Code (With PKCE)&lt;/strong&gt; as the grant type, and fill in the boxes I highlighted below with your &lt;strong&gt;Redirect URI&lt;/strong&gt; , &lt;strong&gt;Authorization Endpoint&lt;/strong&gt; , &lt;strong&gt;Token Endpoint&lt;/strong&gt; , &lt;strong&gt;Client ID&lt;/strong&gt; , &lt;strong&gt;Client Secret&lt;/strong&gt; , and &lt;strong&gt;Scope&lt;/strong&gt; (all from your Entra ID app registration in the Portal):&lt;/p&gt;

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

&lt;p&gt;Notice that for &lt;strong&gt;Scope&lt;/strong&gt; I also added &lt;strong&gt;openid&lt;/strong&gt; and &lt;strong&gt;profile&lt;/strong&gt; , so that I can get not just the &lt;strong&gt;access token&lt;/strong&gt; , but also the &lt;strong&gt;ID token&lt;/strong&gt; with some interesting user info on it.&lt;/p&gt;

&lt;p&gt;And, make sure you select &lt;strong&gt;Send client credentials in body&lt;/strong&gt; for the &lt;strong&gt;Client Authentication&lt;/strong&gt; setting, or it won’t work.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Get New Access Token&lt;/strong&gt; , which should take you to the Entra ID sign-in page. After signing in, you’ll be back in Postman with your new tokens:&lt;/p&gt;

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

&lt;p&gt;Now, grab your &lt;strong&gt;Access Token&lt;/strong&gt; from Postman and decode it in a page like &lt;a href="https://jwt.ms" rel="noopener noreferrer"&gt;jwt.ms&lt;/a&gt;. Here’s my token decoded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "typ": "JWT",
  "alg": "RS256",
  "x5t": "L1KfKFI_jnXbwWc22xZxw1sUHH0",
  "kid": "L1KfKFI_jnXbwWc22xZxw1sUHH0"
}.{
  "aud": "api://8814267c-25fc-459e-b0a6-f6d7ed056f12",
  "iss": "https://sts.windows.net/e6244037-0452-4a93-bcb4-864751f62fcf/",
  "iat": 1716569236,
  "nbf": 1716569236,
  "exp": 1716574166,
  "acr": "1",
  "aio": "AVQAq/8WAAAAk9VaoKrqC7/gpqMXx9XMbpIhpZ9oVzSheQZQr1jCbfJiSkkYd9JgF8ziMf+rrxjonWeQPIAzTpqPtw6DLqjMTIDVsxbtfos6J6ksaoO3BfA=",
  "amr": [
    "pwd",
    "mfa"
  ],
  "appid": "8814267c-25fc-459e-b0a6-f6d7ed056f12",
  "appidacr": "1",
  "family_name": "Casal",
  "given_name": "Julio",
  "ipaddr": "50.35.76.187",
  "name": "Julio Casal",
  "oid": "3f7e659c-1aaf-43ec-8ffd-fb6dc91d1045",
  "rh": "0.AXYAN0Ak5lIEk0q8tIZHUfYvz3wmFIj8JZ5FsKb21-0FbxJ2AO4.",
  "scp": "games:all",
  "sub": "ofM-v1Euz4cuFXSEzmQ1DVtz5W5V27vVR4E5iVHgvFk",
  "tid": "e6244037-0452-4a93-bcb4-864751f62fcf",
  "unique_name": "julioc@dotnetacademy.io",
  "upn": "julioc@dotnetacademy.io",
  "uti": "l6MNUJEh9UmpJOCnIG-nAA",
  "ver": "1.0"
}.[Signature]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can tell exactly which &lt;strong&gt;Audience (aud)&lt;/strong&gt;, &lt;strong&gt;Authority (iss)&lt;/strong&gt;, and &lt;strong&gt;Scopes (scp)&lt;/strong&gt; your backend API will receive in the access tokens, besides a bunch of other interesting claims.&lt;/p&gt;

&lt;p&gt;From that same Postman dialog, scroll down a bit, grab your &lt;strong&gt;id_token&lt;/strong&gt; and decode it. Should be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "L1KfKFI_jnXbwWc22xZxw1sUHH0"
}.{
  "aud": "8814267c-25fc-459e-b0a6-f6d7ed056f12",
  "iss": "https://login.microsoftonline.com/e6244037-0452-4a93-bcb4-864751f62fcf/v2.0",
  "iat": 1716570599,
  "nbf": 1716570599,
  "exp": 1716574499,
  "name": "Julio Casal",
  "oid": "3f7e659c-1aaf-43ec-8ffd-fb6dc91d1045",
  "preferred_username": "julioc@dotnetacademy.io",
  "rh": "0.AXYAN0Ak5lIEk0q8tIZHUfYvz3wmFIj8JZ5FsKb21-0FbxJ2AO4.",
  "sub": "ofM-v1Euz4cuFXSEzmQ1DVtz5W5V27vVR4E5iVHgvFk",
  "tid": "e6244037-0452-4a93-bcb4-864751f62fcf",
  "uti": "5vHcMCJMnkG87UQUAZAbAA",
  "ver": "2.0"
}.[Signature]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;ID Token&lt;/strong&gt; is the one that your Blazor frontend can use to identify the user via interesting claims like &lt;strong&gt;name&lt;/strong&gt; , &lt;strong&gt;preferred_username&lt;/strong&gt; , and &lt;strong&gt;sub&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Knowing about all these claims will save you a lot of trouble down the road, trust me.&lt;/p&gt;

&lt;p&gt;Let’s now start writing some code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Secure the backend API endpoints&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First things first. We have to make sure our endpoints are protected and only accessible to authorized users.&lt;/p&gt;

&lt;p&gt;For this, I first defined a couple of &lt;strong&gt;authorization policies&lt;/strong&gt; in the API backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services
       .AddAuthorizationBuilder()
       .AddPolicy("read_access", builder =&amp;gt;
       {
           builder.RequireClaim("scp", "games:read", "games:all");
       })
       .AddPolicy("write_access", builder =&amp;gt;
       {
           builder.RequireClaim("scp", "games:write", "games:all");
       });

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;read_access&lt;/strong&gt; policy is meant for my GET endpoints, while the &lt;strong&gt;write_access&lt;/strong&gt; policy is meant for all endpoints that can modify my games in any way.&lt;/p&gt;

&lt;p&gt;In each policy, we require the &lt;strong&gt;scp&lt;/strong&gt; claim to be present in the user’s token. From the access token we inspected earlier, we know that is the claim where Entra ID will place the scope(s) the client was granted.&lt;/p&gt;

&lt;p&gt;So, GET endpoints will require either the &lt;strong&gt;games:read&lt;/strong&gt; or &lt;strong&gt;games:all&lt;/strong&gt; scope, while all other endpoints will require either the &lt;strong&gt;games:write&lt;/strong&gt; or &lt;strong&gt;games:all&lt;/strong&gt; scope.&lt;/p&gt;

&lt;p&gt;Here’s one of my GET endpoints, which uses the &lt;strong&gt;read_access&lt;/strong&gt; policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;group.MapGet("/{id}", async (int id, GameStoreContext dbContext) =&amp;gt;
{
    Game? game = await dbContext.Games.FindAsync(id);

    return game is null ? Results.NotFound() : Results.Ok(game.ToGameDetailsDto());
})
.RequireAuthorization("read_access");

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use the &lt;strong&gt;RequireAuthorization&lt;/strong&gt; method to set the policy for each of your endpoints similarly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Configure the backend API for Entra ID&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, we’ll need one NuGet package to be able to use JWT-bearer authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we’ll need to configure our Entra ID details like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services
       .AddAuthentication()
       .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =&amp;gt;
       {
           options.Authority = "https://sts.windows.net/e6244037-0452-4a93-bcb4-864751f62fcf/";
           options.Audience = "api://8814267c-25fc-459e-b0a6-f6d7ed056f12";
           options.MapInboundClaims = false;
       });

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There we are configuring JWT-bearer authentication with the following details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authority:&lt;/strong&gt; The Entra ID authorization server, which is the one that will verify any tokens received by the API. Just replace the GUID there with the &lt;strong&gt;Directory (tenant) ID&lt;/strong&gt; you’ll find in the overview blade of your Entra ID app registration in the Portal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; The Client ID of the Entra ID application for whom the token was issued. With this, the API backend can verify that the token was indeed issued for it. Also found in the overview blade of your Entra ID app registration in the Portal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MapInboundClaims:&lt;/strong&gt; Setting this one to &lt;strong&gt;false&lt;/strong&gt; is important because it will prevent ASP.NET Core from mapping the claims in the token to claim types that we are not interested in. We want to access the scopes directly from the &lt;strong&gt;scp&lt;/strong&gt; claim, and this will allow us to do so.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s a lot more you may want to configure there depending on your scenario, but this will do for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 5: Configure the Blazor frontend for Entra ID&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For this, we’ll need to add one new NuGet package to the Blazor frontend project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, here’s how you can configure the OIDC flow in the frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var authority = "https://login.microsoftonline.com/e6244037-0452-4a93-bcb4-864751f62fcf/v2.0/";

builder.Services
        .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)        
        .AddOpenIdConnect(options =&amp;gt;
        {
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.SignOutScheme = OpenIdConnectDefaults.AuthenticationScheme;
            options.Authority = authority;
            options.ClientId = "8814267c-25fc-459e-b0a6-f6d7ed056f12";
            options.ClientSecret = "[Read from a configuration source]";
            options.ResponseType = OpenIdConnectResponseType.Code;
            options.SaveTokens = true;
            options.MapInboundClaims = false;
            options.Scope.Add("api://8814267c-25fc-459e-b0a6-f6d7ed056f12/games:all");
            options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
            options.TokenValidationParameters.RoleClaimType = "roles";
        })
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break down that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SignInScheme:&lt;/strong&gt; The scheme used to sign in the user. We use cookies here so the user can remain authenticated between requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SignOutScheme:&lt;/strong&gt; The scheme used to sign out the user. We use the OIDC scheme so that the user is signed out of both the OIDC provider and the frontend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authority:&lt;/strong&gt; The Entra ID authorization server, which is the one that will authenticate the user. Use the same GUID you used for &lt;strong&gt;Authority&lt;/strong&gt; in the backend. It’s just that the format of the URL is different.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ClientId:&lt;/strong&gt; The Client ID of the Entra ID application for whom the token will be issued. It’s the same GUID you used for &lt;strong&gt;Audience&lt;/strong&gt; in the backend configuration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ClientSecret:&lt;/strong&gt; The client secret you created in the Entra ID app registration. Make sure you read this from some configuration source, not hardcode it in the code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ResponseType:&lt;/strong&gt; The flow to use to authenticate the user. &lt;strong&gt;OpenIdConnectResponseType.Code&lt;/strong&gt; corresponds to the &lt;strong&gt;Authorization Code Flow&lt;/strong&gt; , which will also use &lt;strong&gt;PKCE&lt;/strong&gt; by default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SaveTokens:&lt;/strong&gt; Whether to save the tokens received from the authorization server. We need to save them so we can use them when we send requests to the backend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MapInboundClaims:&lt;/strong&gt; Same idea as what we did in the backend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; The scope(s) the client wants to request from the authorization server. This is the exact scope we defined when registering the app in the Entra ID portal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TokenValidationParameters.NameClaimType:&lt;/strong&gt; Setting this to &lt;strong&gt;JwtRegisteredClaimNames.Name&lt;/strong&gt; will make sure the user’s name is populated in the claims principal after the user authenticates, so we can easily display it in the frontend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You also need to add these other two lines to your Program.cs in the frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddAuthorizationBuilder();
builder.Services.AddCascadingAuthenticationState();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line adds the authorization services to the frontend, while the second one adds the services needed to propagate the authentication state to all components in the Blazor app.&lt;/p&gt;

&lt;p&gt;We should also add an endpoint in the frontend to kick off the OIDC sign-in flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.MapGet("/authentication/login", () 
    =&amp;gt; TypedResults.Challenge(
        new AuthenticationProperties { RedirectUri = "/" }))
    .AllowAnonymous();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That endpoint will be invoked by an anchor tag in the Blazor Login component. Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="authentication/login" class="btn btn-warning"&amp;gt;Login&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, that configuration should be enough for your frontend to authenticate users via Entra ID.&lt;/p&gt;

&lt;p&gt;But, there’s one more thing needed to let the frontend make authenticated requests to the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 6: Add an authorization handler&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Just because the user is authenticated in the frontend doesn’t mean the backend will automatically trust the frontend to make requests on behalf of the user.&lt;/p&gt;

&lt;p&gt;We need to make sure we attach the &lt;strong&gt;access token&lt;/strong&gt; as a header on every request we make to the backend API endpoints that require authorization.&lt;/p&gt;

&lt;p&gt;We could do this manually in every request, but it’s easier via a delegating handler like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class AuthorizationHandler(IHttpContextAccessor httpContextAccessor) : DelegatingHandler
{
    protected override async Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(
        HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        var httpContext = httpContextAccessor.HttpContext ??
            throw new InvalidOperationException(
                "No HttpContext available from the IHttpContextAccessor!");

        var accessToken = await httpContext.GetTokenAsync("access_token");

        if (!string.IsNullOrEmpty(accessToken))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue(
                "Bearer", 
                accessToken);
        }            

        return await base.SendAsync(request, cancellationToken);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, all we do there is get the access token from the current HTTP context and attach it as a &lt;strong&gt;Bearer&lt;/strong&gt; token in the &lt;strong&gt;Authorization&lt;/strong&gt; header of the request.&lt;/p&gt;

&lt;p&gt;Finally, to enable our authorization hander, we need to register it and add it to the HTTP client in the frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddHttpContextAccessor();
builder.Services.AddTransient&amp;lt;AuthorizationHandler&amp;gt;();     

builder.Services.AddHttpClient&amp;lt;GamesClient&amp;gt;(
    client =&amp;gt; client.BaseAddress = new Uri("http://localhost:5274"))
    .AddHttpMessageHandler&amp;lt;AuthorizationHandler&amp;gt;();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 7: ASP.NET Core + Entra ID in action&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now run your backend and frontend applications and navigate to the frontend in your browser:&lt;/p&gt;

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

&lt;p&gt;How is the home page able to query games without signing in? That’s because I didn’t want users to have to sign in just to be able to list the games, so I didn’t add any authorization policy to the GET endpoint that retrieves all games from the backend.&lt;/p&gt;

&lt;p&gt;Clicking the &lt;strong&gt;Login&lt;/strong&gt; button should take you to a sign-in page like this:&lt;/p&gt;

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

&lt;p&gt;And, after authenticating, you should be redirected back to the frontend:&lt;/p&gt;

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

&lt;p&gt;Thanks to the claims available in the &lt;strong&gt;ID token&lt;/strong&gt; provided by Entra ID, I was able to display the user’s name and generate a Gravatar image where the login button was before.&lt;/p&gt;

&lt;p&gt;I was also able to show &lt;strong&gt;New Game&lt;/strong&gt; , &lt;strong&gt;Edit&lt;/strong&gt; and &lt;strong&gt;Delete&lt;/strong&gt; buttons for each game, which are only visible if the user is authenticated.&lt;/p&gt;

&lt;p&gt;And if I click on the &lt;strong&gt;Edit&lt;/strong&gt; button, I can see the game details and update them:&lt;/p&gt;

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

&lt;p&gt;Both the GET and PUT backend endpoints used by this page require authorization, so if you can load the page and save the updates, the access token was correctly attached to the requests by the authorization handler.&lt;/p&gt;

&lt;p&gt;Mission accomplished!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Whenever you’re ready, there are 4 ways I can help you:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://go.dotnetacademy.io/stripe-waitlist" rel="noopener noreferrer"&gt;​Stripe for .NET Developers (Waitlist)​&lt;/a&gt;&lt;/strong&gt;: Add real payments to your .NET apps with Stripe—fast, secure, production-ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/courses/containers-and-dotnet-aspire" rel="noopener noreferrer"&gt;Containers &amp;amp; .NET Aspire&lt;/a&gt;&lt;/strong&gt;: Build production-ready apps from day 1 and leave ‘but it works on my machine’ behind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;​&lt;a href="https://www.patreon.com/juliocasal" rel="noopener noreferrer"&gt;​Get the full source code&lt;/a&gt;&lt;/strong&gt;: Download the working project from this newsletter, grab exclusive course discounts, and join a private .NET community.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/sponsorship" rel="noopener noreferrer"&gt;Promote your business to 25,000+ developers&lt;/a&gt;&lt;/strong&gt; by sponsoring this newsletter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dotnet</category>
      <category>azure</category>
    </item>
    <item>
      <title>How To Monitor Your ASP.NET Core Application In Azure</title>
      <dc:creator>Julio Casal</dc:creator>
      <pubDate>Sat, 01 Jul 2023 07:00:00 +0000</pubDate>
      <link>https://dev.to/juliocasal/how-to-monitor-your-aspnet-core-application-in-azure-5aj2</link>
      <guid>https://dev.to/juliocasal/how-to-monitor-your-aspnet-core-application-in-azure-5aj2</guid>
      <description>&lt;p&gt;Today I’m going to show you how to quickly start monitoring your ASP.NET Core application in Azure.&lt;/p&gt;

&lt;p&gt;Having good monitoring in place is a huge life saver since it can quickly give you a good insight into the health of your application and works as the pillar upon which you can later setup alerts to make sure you know about application problems before your customers ever notice.&lt;/p&gt;

&lt;p&gt;Many folks leave monitoring for the very end, which is not surprising since it’s not critical to get the app up and running and the traditional series of concepts to learn, libraries to install and services to configure, can be overwhelming.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;You need to start monitoring to your .NET application today.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I’ll show you how to start collecting metrics using &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;, a super popular observability framework to collect, process and export telemetry data and the &lt;a href="https://devblogs.microsoft.com/dotnet/azure-monitor-opentelemetry-distro" rel="noopener noreferrer"&gt;Azure Monitor OpenTelemetry Distro&lt;/a&gt;, a new library to make it really easy to publish collected metrics to Azure Monitor Application Insights.&lt;/p&gt;

&lt;p&gt;With these two sets of frameworks in place you’ll be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add basic monitoring to your ASP.NET Core app in no time&lt;/li&gt;
&lt;li&gt;Quickly see your app’s basic health indicators in your Azure Portal&lt;/li&gt;
&lt;li&gt;Start tracking your custom metrics with a few lines of code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how to get started, step by step:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Prerequisites&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An Azure subscription&lt;/strong&gt;. You can sign up for a free trial here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your existing ASP.NET Core application&lt;/strong&gt;. I’m not sure how much you’ll be able to do with other types of .NET apps. ​
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Create an Application Insights resource&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;In your Azure Portal, look for the Application Insights service and create a new one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t51s9oxm2jafewz0srn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t51s9oxm2jafewz0srn.png" alt="Alt text" width="800" height="827"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once creation is completed (20-30 seconds in my case), go to your new App Insights resource and copy the connection string that you’ll find in the Overview blade. You’ll need that later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb23ef1qn58rsfbe9ahav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb23ef1qn58rsfbe9ahav.png" alt="Alt text" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. Install the client library&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Open a Terminal, switch to your ASP.NET Core app dir and install the Azure Monitor OpenTelemetry Distro client library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add package --prerelease Azure.Monitor.OpenTelemetry.AspNetCore

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;3. Register the Azure Monitor OpenTelemetry services&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Add the following line to your Program.cs file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;builder.Services.AddOpenTelemetry().UseAzureMonitor();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will both register the OpenTelemetry services and configure Azure Monitor exporters for logging, distributed tracing and metrics. The simplest Program.cs file would look like this now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry().UseAzureMonitor();

var app = builder.Build();

app.Run();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;4. Provide the App Insights connection string to your app&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;For your app to start talking to Azure App Insights, it needs the connection string you copied earlier. There are a few ways to configure the connection string in your app, but for local development purposes my preferred approach is to use the .NET Secret Manager.&lt;/p&gt;

&lt;p&gt;To do that, first go back to your Terminal and initialize user secrets for your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet user-secrets init

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, set your secret, where the key must be &lt;code&gt;AzureMonitor:ConnectionString&lt;/code&gt; and the value the actual connection string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet user-secrets set "AzureMonitor:ConnectionString" "YOUR CONN STRING HERE"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;5. Confirm your app is sending data to Azure App Insights&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Surprisingly, there’s nothing else to do to start getting data in App Insights. So, start your app and start hitting a few of your endpoints to start collecting monitoring data.&lt;/p&gt;

&lt;p&gt;Give it a few minutes (data will not show up in real time in Azure) and eventually you should see a few essential metrics popping up in your App Insights Overview blade:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08jjuwzw07w7uucju1zj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F08jjuwzw07w7uucju1zj.png" alt="Alt text" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I even tried out a few requests that resulted in a 404 to confirm they would show up as failed requests in the first chart.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;6. Track custom metrics&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Let’s say that now you want to start tracking your own metrics. For instance, in my little Match Making app I’d like to start counting each time a new match is created and report that as a new metric.&lt;/p&gt;

&lt;p&gt;That’s actually quite easy with the built in APIs provided in .NET. So here’s what you do:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Define a counter:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create an instance of the meter for your API and use it to create your counter:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Count! For instance, here’s where my app creates a new match, so just after the match is created in my repository, I use the counter to count one more match created:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Define your new meter in your Program.cs via the ConfigureOpenTelemetryMeterProvider method:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run your app and execute the endpoint(s) that use the above logic. Then go to your Metrics blade in the Azure portal and add your new metric. In my case, the created matches look like this:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ew3jnbcgrd48d0d5h2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ew3jnbcgrd48d0d5h2q.png" alt="Alt text" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Done!&lt;/p&gt;

&lt;p&gt;Well, that’s it for today.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Whenever you’re ready, there are 4 ways I can help you:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/waitlist" rel="noopener noreferrer"&gt;​Stripe for .NET Developers (Waitlist)​&lt;/a&gt;&lt;/strong&gt;: Add real payments to your .NET apps with Stripe—fast, secure, production-ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/courses/containers-and-dotnet-aspire" rel="noopener noreferrer"&gt;Containers &amp;amp; .NET Aspire&lt;/a&gt;&lt;/strong&gt;: Build production-ready apps from day 1 and leave ‘but it works on my machine’ behind.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;​&lt;a href="https://www.patreon.com/juliocasal" rel="noopener noreferrer"&gt;​Get the full source code&lt;/a&gt;&lt;/strong&gt;: Download the working project from this newsletter, grab exclusive course discounts, and join a private .NET community.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://juliocasal.com/sponsorship" rel="noopener noreferrer"&gt;Promote your business to 25,000+ developers&lt;/a&gt;&lt;/strong&gt; by sponsoring this newsletter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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