<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Tore Nestenius</title><description>Blog by Tore Nestenius — .NET, C#, architecture, web security, and OpenID Connect.</description><link>https://nestenius.se/</link><language>en</language><item><title>Visualizing Claude Code MCP Requests with Coding Agent Explorer</title><link>https://nestenius.se/ai/visualizing-claude-code-mcp-requests-with-coding-agent-explorer/</link><guid isPermaLink="true">https://nestenius.se/ai/visualizing-claude-code-mcp-requests-with-coding-agent-explorer/</guid><description>Learn how to use the MCP Observer in the Coding Agent Explorer to intercept and visualize all traffic between Claude Code and any MCP server in a real-time dashboard.</description><pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://modelcontextprotocol.io&quot;&gt;Model Context Protocol&lt;/a&gt; (MCP) servers are becoming a key part of how &lt;a href=&quot;https://claude.com/product/claude-code&quot;&gt;Claude Code&lt;/a&gt; extends its capabilities. They give the agent access to documentation, code search, external APIs, and much more. But when Claude Code talks to an MCP server, what exactly is it saying? And what is the server sending back?&lt;/p&gt;
&lt;p&gt;Until now, that communication has been invisible. The &lt;strong&gt;MCP Observer&lt;/strong&gt; changes that. It is a new feature in the Coding Agent Explorer that intercepts all traffic between Claude Code and any MCP server and displays it in a real-time dashboard, with both a raw JSON view and a readable, formatted view that makes sense of the data.&lt;/p&gt;
&lt;p&gt;Here is what you will learn in this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What MCP is and why it matters for coding agents&lt;/li&gt;
&lt;li&gt;What the MCP Observer does and how it works&lt;/li&gt;
&lt;li&gt;How to configure it and connect it to any MCP server&lt;/li&gt;
&lt;li&gt;What you can see in the dashboard&lt;/li&gt;
&lt;li&gt;A few MCP services to try straight away&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a multi-part series on the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt;, an open-source .NET tool for inspecting what AI coding agents do under the hood.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Part 1 - Introducing the Coding Agent Explorer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/&quot;&gt;Part 2 - Exploring Claude Code Hooks with the Coding Agent Explorer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3 - Visualizing Claude Code MCP Requests with the Coding Agent Explorer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-is-model-context-protocol-mcp&quot;&gt;What Is Model Context Protocol (MCP)?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://modelcontextprotocol.io&quot;&gt;Model Context Protocol&lt;/a&gt; is an open standard that lets AI agents connect to external tools and data sources. An MCP server exposes a set of tools that the agent can discover and call, just like a REST API but designed specifically for AI agent communication.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing Claude Code agents communicating with MCP servers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;834&quot; height=&quot;205&quot; src=&quot;https://nestenius.se/_astro/claude-code-agents-talking-to-mcp-servers.DchQBRST_1hAtmt.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Claude Code has built-in support for MCP. You can register any MCP server, and Claude Code will automatically discover the tools it provides and use them whenever they are relevant to the task at hand.&lt;/p&gt;
&lt;p&gt;The protocol is built on &lt;strong&gt;JSON-RPC 2.0.&lt;/strong&gt; When Claude Code connects to an MCP server, it sends messages like initialize, tools/list, and tools/call. The server responds with its capabilities and tool results. This is the conversation the MCP Observer lets you see.&lt;/p&gt;
&lt;h2 id=&quot;http-vs-stdio-mcp-servers&quot;&gt;HTTP vs. STDIO MCP Servers&lt;/h2&gt;
&lt;p&gt;MCP servers can communicate over two transports: &lt;strong&gt;HTTP&lt;/strong&gt; and &lt;strong&gt;STDIO&lt;/strong&gt;. Claude Code supports both.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MCP HTTP vs STDIO transport comparison diagram&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1358&quot; height=&quot;385&quot; src=&quot;https://nestenius.se/_astro/mcp-http-vs-stdio-transport.1JwpRn9v_Z114zwI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is the difference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HTTP servers&lt;/strong&gt;&lt;br&gt;
Expose a URL that Claude Code connects to over the network. The traffic is standard HTTP, which means a proxy can sit in the middle and capture it transparently. This is what the MCP Observer uses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;STDIO servers&lt;/strong&gt;&lt;br&gt;
Run as a local process. Claude Code launches the process and communicates with it over stdin and stdout. The MCP Observer cannot intercept this communication, so STDIO-based servers are not visible in the observer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That said, Claude Code’s &lt;strong&gt;PreToolUse&lt;/strong&gt; and &lt;strong&gt;PostToolUse&lt;/strong&gt; hooks fire for MCP tool calls regardless of transport. They do not show the raw JSON-RPC protocol, but they do show which MCP tool was called and what parameters were passed. That is enough to understand what the agent is doing, even without the protocol detail. If you have not read the &lt;a href=&quot;https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/&quot;&gt;previous post on hooks&lt;/a&gt;, it is a good companion to this one.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-mcp-observer&quot;&gt;What Is the MCP Observer?&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;MCP Observer&lt;/strong&gt; is a transparent proxy that runs on &lt;strong&gt;port 9999&lt;/strong&gt; (HTTP). Claude Code thinks it is talking to the real MCP server. In reality, it is talking to the observer, which forwards every request to the real server, captures the traffic, and displays it on the dashboard.&lt;/p&gt;
&lt;p&gt;Here is how the data flows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MCP Observer proxy data flow between Claude Code and MCP service&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1256&quot; height=&quot;711&quot; src=&quot;https://nestenius.se/_astro/mcp-observer-proxy-data-flow.BOMYc_EO_Z17BYTE.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Claude Code gets the real responses without any modification. The observer is completely transparent. You can add it to any MCP workflow without changing how Claude Code or the MCP server behaves.&lt;/p&gt;
&lt;h2 id=&quot;why-http-only-on-port-9999&quot;&gt;Why HTTP Only on Port 9999?&lt;/h2&gt;
&lt;p&gt;The proxy between Claude Code and the observer runs over plain HTTP, not HTTPS. This is intentional. The connection is local-only: both Claude Code and the observer run on the same machine, so the traffic never leaves your computer and encryption adds no security benefit. Using HTTP also avoids the need to set up a local certificate, which would make the setup much more involved.&lt;/p&gt;
&lt;p&gt;The outbound connection from the observer to the real MCP server is a separate matter. The observer forwards requests to whatever URL you configure, and that connection uses whatever scheme the server requires. In practice, all public MCP servers use HTTPS, so the traffic between the observer and the real server is always encrypted.&lt;/p&gt;
&lt;h2 id=&quot;how-to-set-up-the-mcp-observer&quot;&gt;How to Set Up the MCP Observer&lt;/h2&gt;
&lt;p&gt;Setting up the MCP Observer takes about two minutes.&lt;/p&gt;
&lt;h3 id=&quot;step-1-start-the-coding-agent-explorer&quot;&gt;Step 1: Start the Coding Agent Explorer&lt;/h3&gt;
&lt;p&gt;First, run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts the MCP proxy on port &lt;strong&gt;9999&lt;/strong&gt; and the existing proxy on port &lt;strong&gt;8888&lt;/strong&gt;, as well as the dashboard on ports &lt;strong&gt;5000&lt;/strong&gt; and &lt;strong&gt;5001&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For details on how to install, build, and run the Coding Agent Explorer, see the first post in this series: &lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Introducing the Coding Agent Explorer&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-2-create-a-working-directory&quot;&gt;Step 2: Create a working directory&lt;/h3&gt;
&lt;p&gt;Create a fresh folder where you will run &lt;strong&gt;claude&lt;/strong&gt;. Then open a terminal in that folder before running the next step.&lt;/p&gt;
&lt;h3 id=&quot;step-3-register-the-proxy-with-claude-code&quot;&gt;Step 3: Register the proxy with Claude Code&lt;/h3&gt;
&lt;p&gt;Run this command in your terminal from inside your working directory:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --transport&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; http&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mcp_proxy&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; http://localhost:9999&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default this uses &lt;strong&gt;local scope&lt;/strong&gt;: the registration is stored in &lt;strong&gt;~/.claude.json&lt;/strong&gt; under your project path and is only active when Claude Code is started from this directory.&lt;/p&gt;
&lt;p&gt;The URL &lt;code&gt;http://localhost:9999&lt;/code&gt; is the fixed address of the MCP Observer proxy and never changes. Which real MCP server the traffic is forwarded to is controlled separately inside the MCP Observer dashboard. You only need to run this command once.&lt;/p&gt;
&lt;p&gt;To remove the registration later:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claude&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mcp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; remove&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; mcp_proxy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For full details on scopes and other options, see the &lt;a href=&quot;https://code.claude.com/docs/en/mcp#installing-mcp-servers&quot;&gt;Claude Code MCP documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-4-verify-the-mcp-configuration&quot;&gt;Step 4: Verify the MCP configuration&lt;/h3&gt;
&lt;p&gt;Start &lt;strong&gt;Claude Code&lt;/strong&gt; and run the &lt;strong&gt;/mcp&lt;/strong&gt; command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;/mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lists all registered MCP servers and their connection status. You should see &lt;strong&gt;mcp_proxy&lt;/strong&gt; listed as connected. If the status shows an error, check that the Coding Agent Explorer is running and that a destination URL has been set in the MCP Observer dashboard.&lt;/p&gt;
&lt;h3 id=&quot;step-5-open-the-mcp-observer-dashboard&quot;&gt;Step 5: Open the MCP Observer dashboard&lt;/h3&gt;
&lt;p&gt;Open the dashboard at &lt;code&gt;https://localhost:5001&lt;/code&gt; and click &lt;strong&gt;MCP Observer&lt;/strong&gt; in the navigation bar.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Coding Agent Explorer main menu showing MCP Observer navigation item&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1353&quot; height=&quot;465&quot; src=&quot;https://nestenius.se/_astro/coding-agent-explorer-mcp-observer-menu.BIYNzGGp_Z15N3Yf.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-6-set-the-destination-url&quot;&gt;Step 6: Set the destination URL&lt;/h3&gt;
&lt;p&gt;The destination URL field is pre-filled with &lt;strong&gt;&lt;a href=&quot;https://gitmcp.io/tndata/CloudDebugger&quot;&gt;https://gitmcp.io/tndata/CloudDebugger&lt;/a&gt;&lt;/strong&gt; as a ready-to-use default. This is the MCP endpoint for &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;Cloud Debugger&lt;/a&gt;, another open-source project by me. It is a good starting point if you just want to try the observer without setting up your own MCP server.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/idosal/git-mcp&quot;&gt;GitMCP&lt;/a&gt; is a free, open-source service that turns any GitHub repository into a remote MCP server, giving AI tools access to up-to-date documentation and code directly from the source.&lt;/p&gt;
&lt;p&gt;Leave the default URL in place and click &lt;strong&gt;Set&lt;/strong&gt; to activate it. The status line updates to confirm the proxy is active and forwarding traffic.&lt;/p&gt;
&lt;p&gt;When you click &lt;strong&gt;Set&lt;/strong&gt;, the Coding Agent Explorer updates its internal destination and reconfigures the YARP reverse proxy on &lt;strong&gt;port 9999&lt;/strong&gt; to forward traffic to the new URL. No restart is needed. Only one destination can be active at a time. If you change it, the request history is cleared so that the table always reflects a single server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you change the destination URL while Claude Code is already running, &lt;strong&gt;restart Claude Code before continuing&lt;/strong&gt;. Claude Code caches tool information from the MCP server when it connects, and it will not pick up the new server’s tools until it reconnects.&lt;/p&gt;
&lt;h3 id=&quot;step-7-try-your-first-prompt&quot;&gt;Step 7: Try your first prompt&lt;/h3&gt;
&lt;p&gt;The Cloud Debugger is an open-source .NET tool for debugging live Azure applications. It can capture and display HTTP requests, exceptions, service bus messages, and more, without attaching a debugger or redeploying.&lt;/p&gt;
&lt;p&gt;With the MCP Observer active and the Cloud Debugger endpoint set, start Claude Code from your working directory and try one of these prompts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;What is the Cloud Debugger and what can it do?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does the Cloud Debugger include any Python code?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which Azure services does the Cloud Debugger support?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Watch the &lt;strong&gt;MCP Observer&lt;/strong&gt; dashboard as Claude Code connects: you will see the &lt;strong&gt;initialize&lt;/strong&gt; and &lt;strong&gt;tools/list&lt;/strong&gt; calls appear first, followed by one or more &lt;strong&gt;tools/call&lt;/strong&gt; requests as Claude Code fetches the information it needs to answer your question.&lt;/p&gt;
&lt;h2 id=&quot;what-you-can-see&quot;&gt;What You Can See&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;MCP Observer&lt;/strong&gt; captures every request and response and displays them in a table, sorted oldest to newest. Each row shows the time, HTTP method, path, JSON-RPC method, response status, and duration.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MCP Observer request list showing JSON-RPC calls between Claude Code and MCP server&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;920&quot; height=&quot;497&quot; src=&quot;https://nestenius.se/_astro/mcp-observer-request-list.tP7qHgyh_1ix9Q.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;tools/call&lt;/strong&gt; requests, the JSON-RPC method column shows the name of the tool that was called in parentheses, so you can see at a glance which tools Claude Code is using. For example: &lt;strong&gt;tools/call (search_docs)&lt;/strong&gt; or &lt;strong&gt;tools/call (fetch_page)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Click any row to open the detail panel at the bottom, which shows the request body on the left and the response on the right.&lt;/p&gt;
&lt;h2 id=&quot;two-ways-to-view-the-response&quot;&gt;Two Ways to View the Response&lt;/h2&gt;
&lt;p&gt;The response panel has two view modes: &lt;strong&gt;Pretty&lt;/strong&gt; and &lt;strong&gt;Raw&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;pretty&quot;&gt;Pretty&lt;/h3&gt;
&lt;p&gt;This is the default. It renders the response in a readable format that depends on the type of request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;tools/list&lt;/strong&gt; responses are shown as a card for each tool, with the tool name, description, and input parameters clearly laid out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;initialize&lt;/strong&gt; responses show the protocol version, server name and version, and capabilities as a simple key-value list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tools/call&lt;/strong&gt; responses show the returned content directly, so you can read the actual text the MCP server returned.&lt;/li&gt;
&lt;li&gt;All other responses fall back to formatted JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;MCP Observer pretty view showing formatted tool response&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;834&quot; height=&quot;421&quot; src=&quot;https://nestenius.se/_astro/mcp-observer-pretty-view.OqSr2vZ9_2utGaG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;raw&quot;&gt;Raw&lt;/h3&gt;
&lt;p&gt;Shows the full JSON, pretty-printed, so you can inspect every field.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MCP Observer raw JSON view of MCP server response&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;796&quot; height=&quot;359&quot; src=&quot;https://nestenius.se/_astro/mcp-observer-raw-json-view.zVbqB5eu_MpkRG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;sample-mcp-services-to-try&quot;&gt;Sample MCP Services to Try&lt;/h2&gt;
&lt;p&gt;Here are two public MCP services that work well as starting points.&lt;/p&gt;
&lt;h3 id=&quot;microsoft-learn&quot;&gt;Microsoft Learn&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://learn.microsoft.com/en-us/training/support/mcp&quot;&gt;Microsoft Learn&lt;/a&gt; MCP server gives Claude Code access to the full suite of Microsoft’s documentation, including Azure, .NET, and the rest of Microsoft’s product ecosystem.&lt;/p&gt;
&lt;p&gt;Destination URL:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://learn.microsoft.com/api/mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once registered, try asking Claude Code:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I create an Azure Container App using the az CLI?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You will see Claude Code call &lt;strong&gt;tools/list&lt;/strong&gt; to discover the available tools, and then it will call one or more of them to fetch the relevant documentation, and finally it uses the results to answer your question. The MCP Observer shows each of those calls as they happen.&lt;/p&gt;
&lt;h3 id=&quot;context7&quot;&gt;Context7&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://context7.com/&quot;&gt;Context7&lt;/a&gt; provides up-to-date documentation for popular libraries and frameworks. It is particularly useful for questions about rapidly changing ecosystems like Next.js, where training data can be out of date.&lt;/p&gt;
&lt;p&gt;Destination URL:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://mcp.context7.com/mcp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once registered, try asking Claude Code:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I set up middleware in Next.js 15? Use context7&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Use context7&lt;/strong&gt; hint tells Claude Code to prioritise the &lt;strong&gt;Context7&lt;/strong&gt; &lt;strong&gt;MCP&lt;/strong&gt; server for this query. Watch the MCP Observer to see how it resolves the library ID, fetches the relevant documentation, and uses it to answer your question.&lt;/p&gt;
&lt;h2 id=&quot;a-practical-example-watching-a-toolscall-in-action&quot;&gt;A Practical Example: Watching a tools/call in Action&lt;/h2&gt;
&lt;p&gt;To make this concrete, here is what happens when you ask Claude Code the Azure Container App question above.&lt;/p&gt;
&lt;p&gt;You type your prompt. Claude Code connects to the MCP server and the observer captures the following sequence:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Microsoft Learn MCP server tools/list and tools/call requests in MCP Observer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;924&quot; height=&quot;557&quot; src=&quot;https://nestenius.se/_astro/microsoft-learn-mcp-request.CU_vWih6_Z23WofI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Clicking the &lt;strong&gt;tools/list&lt;/strong&gt; row in &lt;strong&gt;Pretty view&lt;/strong&gt; shows all the tools the Microsoft Learn MCP server exposes: their names, descriptions, and accepted parameters. Clicking the &lt;strong&gt;tools/call&lt;/strong&gt; row shows exactly what Claude Code asked for and what the server returned.&lt;/p&gt;
&lt;h2 id=&quot;mcp-requests-and-hooks&quot;&gt;MCP Requests and Hooks&lt;/h2&gt;
&lt;p&gt;The MCP Observer shows you the protocol-level conversation between Claude Code and the MCP server. But there is a complementary view available in the Conversation View if you have hooks configured.&lt;/p&gt;
&lt;p&gt;Claude Code fires &lt;strong&gt;PreToolUse&lt;/strong&gt; and &lt;strong&gt;PostToolUse&lt;/strong&gt; hook events for every MCP tool call, just as it does for built-in tools like &lt;strong&gt;Read&lt;/strong&gt; or &lt;strong&gt;Bash&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;MCP Observer&lt;/strong&gt; and the &lt;strong&gt;Conversation View&lt;/strong&gt; complement each other. The observer shows you the raw protocol detail. The Conversation View with hooks shows you the full picture of what the agent was doing and why.&lt;/p&gt;
&lt;p&gt;Here is an example of a PreToolUse call:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Claude Code PreToolUse hook event for MCP tool call&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;851&quot; height=&quot;868&quot; src=&quot;https://nestenius.se/_astro/claude-code-pretooluse-mcp-hook.BpDZ31Pl_1bb2hB.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you have set up &lt;strong&gt;HookAgent&lt;/strong&gt; as described in the &lt;a href=&quot;https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/&quot;&gt;previous post in this series&lt;/a&gt;, those events appear in the Conversation View alongside the LLM API calls. This lets you see MCP tool usage in context: you can watch the LLM call that triggered the tool call, the hook events that fired around it, and the next LLM call that consumed the result, all on the same timeline.&lt;/p&gt;
&lt;h2 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h2&gt;
&lt;p&gt;Most developers who start using MCP servers treat them as a black box. You register a server, Claude Code uses it, and something useful happens. But what tools does the server expose? What does Claude Code actually ask it? What does the server send back?&lt;/p&gt;
&lt;p&gt;Those are exactly the questions the MCP Observer answers. Once you can see the traffic, you can evaluate whether a server is giving Claude Code good information, whether the tool descriptions are clear enough for the model to use them correctly, and whether the responses are fast enough to be practical.&lt;/p&gt;
&lt;p&gt;For anyone building their own MCP server, the observer is invaluable. You can see exactly how Claude Code interacts with your server during development, without adding any logging to the server itself.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;The Coding Agent Explorer now covers three layers of Claude Code’s operation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLM API (HTTP Inspector and Conversation View)&lt;/li&gt;
&lt;li&gt;Lifecycle events (hooks)&lt;/li&gt;
&lt;li&gt;MCP servers (MCP Observer)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Together they give you a complete picture of what the agent is doing at every level.&lt;/p&gt;
&lt;p&gt;The full project is open-source and available on GitHub: &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;github.com/tndata/CodingAgentExplorer&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;i-want-your-feedback&quot;&gt;I Want Your Feedback!&lt;/h2&gt;
&lt;p&gt;If you try the MCP Observer and find a bug, have a feature request, or want to share your experience, please &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer/issues&quot;&gt;create an issue on GitHub&lt;/a&gt;. Contributions are welcome. The codebase is intentionally simple to make it easy to jump in.&lt;/p&gt;
&lt;h2 id=&quot;want-to-learn-agentic-development&quot;&gt;Want to Learn Agentic Development?&lt;/h2&gt;
&lt;p&gt;I am currently working on a workshop called &lt;strong&gt;Agentic Development with Claude Code&lt;/strong&gt; where we use the Coding Agent Explorer to explore how coding agents work under the hood. The MCP Observer is one of the tools I use to show participants exactly how Claude Code discovers and uses external tools.&lt;/p&gt;
&lt;p&gt;You can read more about my workshops here: &lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;tn-data.se/courses&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also give a presentation called &lt;a href=&quot;https://tn-data.se/talks/ai-development/&quot;&gt;How Does a Coding Agent Work?&lt;/a&gt; for companies and conferences. &lt;a href=&quot;https://tn-data.se/&quot;&gt;Contact me&lt;/a&gt; if you are interested.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h2&gt;
&lt;h3 id=&quot;does-the-mcp-observer-affect-claude-code-or-the-mcp-server-in-any-way&quot;&gt;Does the MCP Observer affect Claude Code or the MCP server in any way?&lt;/h3&gt;
&lt;p&gt;No. The observer forwards requests and responses without modification. Both Claude Code and the MCP server behave exactly as they would without the proxy.&lt;/p&gt;
&lt;h3 id=&quot;can-i-use-the-mcp-observer-with-any-mcp-server&quot;&gt;Can I use the MCP Observer with any MCP server?&lt;/h3&gt;
&lt;p&gt;Yes, as long as the server uses the streamable HTTP transport (which all modern MCP servers do). Enter the server’s URL in the destination field and click Set.&lt;/p&gt;
&lt;h3 id=&quot;can-i-watch-multiple-mcp-servers-at-the-same-time&quot;&gt;Can I watch multiple MCP servers at the same time?&lt;/h3&gt;
&lt;p&gt;Not currently. The observer is configured for one destination at a time. To switch servers, enter the new URL and click Set. The request history is cleared when you change the destination. After changing the URL you must also restart Claude Code, as it caches tool information from the MCP server at startup and will not pick up the new server’s tools until it reconnects.&lt;/p&gt;
&lt;h3 id=&quot;why-does-changing-the-destination-clear-the-request-history&quot;&gt;Why does changing the destination clear the request history?&lt;/h3&gt;
&lt;p&gt;Mixing requests from different servers in the same list would make the timeline confusing and hard to read. Clearing on change keeps the view focused on the server you are currently observing.&lt;/p&gt;
&lt;h3 id=&quot;do-i-need-to-restart-claude-code-after-registering-the-proxy&quot;&gt;Do I need to restart Claude Code after registering the proxy?&lt;/h3&gt;
&lt;p&gt;Always start Claude Code after the Coding Agent Explorer is already running and after you have set the destination URL in the MCP Observer dashboard. Claude Code connects to the MCP server during startup to discover its tools. If the proxy is not yet active when Claude Code starts, it will not find any tools.&lt;/p&gt;
&lt;h3 id=&quot;can-i-observe-stdio-based-mcp-servers&quot;&gt;Can I observe STDIO-based MCP servers?&lt;/h3&gt;
&lt;p&gt;Not with the MCP Observer, which only works with HTTP transport. However, Claude Code fires &lt;code&gt;PreToolUse&lt;/code&gt; and &lt;code&gt;PostToolUse&lt;/code&gt; hook events for every MCP tool call regardless of transport. If you have hooks configured, those events appear in the Conversation View and show you which tool was called and what parameters were passed. It is a higher-level view than the raw JSON-RPC protocol, but it gives you meaningful visibility into what the agent is doing. See the &lt;a href=&quot;https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/&quot;&gt;previous post in this series&lt;/a&gt; for details on setting up hooks.&lt;/p&gt;
&lt;h2 id=&quot;related-posts&quot;&gt;Related Posts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Introducing the Coding Agent Explorer (.NET)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/how-to-use-kurrentdb-for-event-sourcing-in-c-on-azure/&quot;&gt;How to Use KurrentDB for Event Sourcing in C# on Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/discovering-net-codebases-using-code-coverage-and-ncrunch/&quot;&gt;Discovering .NET codebases using code coverage and NCrunch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>ai</category></item><item><title>Exploring Claude Code Hooks with the Coding Agent Explorer (.NET)</title><link>https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/</link><guid isPermaLink="true">https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/</guid><description>Ever wondered what Claude Code is actually doing while it works? Every file it reads, every command it runs, every permission it requests. Claude Code</description><pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ever wondered what &lt;a href=&quot;https://claude.com/product/claude-code&quot;&gt;Claude Code&lt;/a&gt; is actually doing while it works? Every file it reads, every command it runs, every permission it requests. Claude Code exposes all of this through a hook system that lets you intercept and observe each step.&lt;/p&gt;
&lt;p&gt;In this post, you’ll learn what hooks are, how to set them up, and how to use the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt; to visualize hook events in real time.&lt;/p&gt;
&lt;p&gt;The post is part of a multi-part series on the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt;, an open-source .NET tool for inspecting what AI coding agents do under the hood. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Part 1 - Introducing the Coding Agent Explorer (.NET)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 2 - Exploring Claude Code Hooks with the Coding Agent Explorer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Part 3 - Visualizing Claude Code MCP Requests with the Coding Agent Explorer (coming soon)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make this more concrete, the diagram below shows how Claude Code emits hook events, how each event triggers a call to HookAgent, and how those events are then forwarded to the Coding Agent Explorer for visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Claude Code Hooks Flow with HookAgent and Coding Agent Explorer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;511&quot; src=&quot;https://nestenius.se/_astro/claude-code-hooks-hookagent-coding-agent-explorer-diagram-1024x511.C9ESRDCd_Z1awJS8.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Each time a hook event occurs, Claude Code invokes HookAgent, which receives the event data and forwards it to the Coding Agent Explorer, where it appears in the dashboard in real time.&lt;/p&gt;
&lt;h2 id=&quot;what-are-claude-code-hooks&quot;&gt;What Are Claude Code Hooks?&lt;/h2&gt;
&lt;p&gt;When things happen inside Claude Code, it can execute a hook. For example, when Claude Code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading or writing a file&lt;/li&gt;
&lt;li&gt;Running a shell command&lt;/li&gt;
&lt;li&gt;Requesting a permission&lt;/li&gt;
&lt;li&gt;Starting or ending a session&lt;/li&gt;
&lt;li&gt;Submitting a user prompt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The diagram below highlights several of the hook events that Claude Code generates during execution.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Claude Code hook events diagram showing SessionStart, PreToolUse, Notification, and SubagentStart events&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;612&quot; height=&quot;577&quot; src=&quot;https://nestenius.se/_astro/Claude_code_exposing_hooks.Fv_NDHiF_QjKSf.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A hook is simply a shell command that Claude Code runs at that point, passing a JSON payload describing the event via &lt;strong&gt;standard input (stdin)&lt;/strong&gt;. This gives you a way to extend, observe, and control Claude Code’s behavior from the outside.&lt;/p&gt;
&lt;h2 id=&quot;what-can-hooks-be-used-for&quot;&gt;What can hooks be used for?&lt;/h2&gt;
&lt;p&gt;Hooks turn Claude Code from a black box into something you can observe, control, and extend. In practice, they let you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enforce security policies before tools run&lt;/li&gt;
&lt;li&gt;Log and audit tool usage&lt;/li&gt;
&lt;li&gt;Trigger external automation&lt;/li&gt;
&lt;li&gt;Notify external systems (Slack, CI, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, hooks let you move from observing the agent to controlling how it behaves.&lt;/p&gt;
&lt;h2 id=&quot;example-block-access-to-secret-files&quot;&gt;Example: Block access to secret files&lt;/h2&gt;
&lt;p&gt;A classic use case is intercepting &lt;strong&gt;PreToolUse&lt;/strong&gt; &lt;strong&gt;events&lt;/strong&gt; to block the agent from accessing files it should not touch.&lt;/p&gt;
&lt;p&gt;Let’s say we have a file named &lt;strong&gt;Secret.key&lt;/strong&gt; that we never want Claude Code to read. When the agent attempts to access it, your PreToolUse hook intercepts the request, inspects the file path, and blocks the operation by returning a &lt;strong&gt;non-zero&lt;/strong&gt; exit code with an error message. Claude Code respects the response and continues without ever opening the file.&lt;/p&gt;
&lt;p&gt;The diagram below illustrates how the &lt;strong&gt;PreToolUse&lt;/strong&gt; hook intercepts and blocks the request.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram illustrating how a PreToolUse hook prevents Claude Code from accessing a sensitive file like secret.key&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;258&quot; src=&quot;https://nestenius.se/_astro/claude-code-hooks-block-file-access-secret-key-1024x258.D2925JxK_ZSG1km.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s what Claude Code sends to the hook via &lt;strong&gt;stdin&lt;/strong&gt; when it tries to read &lt;strong&gt;secret.key&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;session_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;9ec4714b-3475-4773-b4af-3f70d7fe68f7&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;transcript_path&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;C:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Users&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Tore&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.claude&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;projects&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;C--Conf&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;9ec4714b-3475-4773-b4af-3f70d7fe68f7.jsonl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;cwd&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;C:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;MyApp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;permission_mode&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;hook_event_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;PreToolUse&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tool_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Read&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tool_input&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;file_path&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;C:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;MyApp&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Secret.key&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tool_use_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;toolu_01BQ4EQBtbxbFRoTEc1CXRgf&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-can-a-hook-control-claude-code&quot;&gt;How can a hook control Claude Code?&lt;/h2&gt;
&lt;p&gt;Hooks are not just for observing behavior; they can actively influence what Claude Code is allowed to do.&lt;/p&gt;
&lt;p&gt;When a hook runs, it returns an exit code and an optional message. Claude Code uses this response to decide what happens next:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exit code 0&lt;/strong&gt;&lt;br&gt;
allow the action to proceed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-zero exit code&lt;/strong&gt;&lt;br&gt;
block the action&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The optional message is just as important. When an action is blocked, the message is passed back to Claude Code and becomes part of the model’s context. Instead of just failing, the agent receives feedback it can use to adjust its behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In other words, the exit code enforces the decision, while the message helps guide what happens next&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;which-events-can-we-hook-into&quot;&gt;Which events can we hook into?&lt;/h2&gt;
&lt;p&gt;Claude Code provides four categories of hookable events:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tool Events&lt;/strong&gt; - PreToolUse, PostToolUse, PostToolUseFailure,..&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session Events&lt;/strong&gt; - SessionStart, SessionEnd, PreCompact,…&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subagent Lifecycle&lt;/strong&gt; - SubagentStart, SubagentStop, TaskCompleted, …&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Interaction&lt;/strong&gt; - UserPromptSubmit, Notification, PermissionRequest, …&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The diagram below gives a high-level overview of how hook events are grouped and emitted by Claude Code.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing Claude Code hook events including PreToolUse, PostToolUse, SessionStart, and UserPromptSubmit categories&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;555&quot; src=&quot;https://nestenius.se/_astro/claude-code-hook-events-categories-diagram-1024x555.C1AAeylZ_Pe8i4.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;A complete reference for all events is included later in this post.&lt;/p&gt;
&lt;h2 id=&quot;why-hooks-matter-for-every-project&quot;&gt;Why Hooks Matter for Every Project&lt;/h2&gt;
&lt;p&gt;Beyond observability, hooks are one of the most important control points available in any Claude Code project. By registering a &lt;strong&gt;PreToolUse&lt;/strong&gt; &lt;strong&gt;hook&lt;/strong&gt;, you get to run your own code before any tool executes, giving you the opportunity to inspect, block, or log every action the agent attempts.&lt;/p&gt;
&lt;p&gt;This is a practical and effective way to restrict how Claude Code interacts with tools and files. You can block reads and writes to sensitive files and directories (credentials, &lt;code&gt;.env&lt;/code&gt; files, private keys, config folders), prevent specific shell commands from running, and define clear rules around what the agent is allowed to touch.&lt;/p&gt;
&lt;p&gt;Because your hook runs &lt;strong&gt;in your environment&lt;/strong&gt;, it is always invoked before the tool executes and cannot be skipped through prompt manipulation alone. However, the effectiveness of the protection depends on how your hook validates and enforces its rules.&lt;/p&gt;
&lt;p&gt;Hooks also provide a meaningful defense against &lt;strong&gt;p****rompt injection attacks&lt;/strong&gt;. If a malicious file or web page contains hidden instructions telling Claude Code to exfiltrate data or modify a sensitive file, a PreToolUse hook can catch the attempt before the tool executes and block it with a non-zero exit code. Claude Code reads your error message and stops, preventing the action from reaching the file system.&lt;/p&gt;
&lt;p&gt;In practice, hooks act as a powerful guardrail and auditing layer. For &lt;strong&gt;stronger guarantees&lt;/strong&gt;, especially when shell access is enabled, they should be combined with environment-level controls such as filesystem restrictions or sandboxing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In other words, hooks are one layer in a broader security model and should be combined with other controls for robust protection.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-to-observe-claude-code-hooks-in-real-time&quot;&gt;How to Observe Claude Code Hooks in Real Time&lt;/h2&gt;
&lt;p&gt;But how can we observe exactly when hooks are called and what data is passed to them? That is where the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt; and its &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer/tree/main/HookAgent&quot;&gt;HookAgent tool&lt;/a&gt; come in.&lt;/p&gt;
&lt;p&gt;Here is what the rest of this post covers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How the HookAgent tool bridges Claude Code and the dashboard&lt;/li&gt;
&lt;li&gt;How to set everything up in a few minutes&lt;/li&gt;
&lt;li&gt;What you can actually see once it is running&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;introducing-hookagent&quot;&gt;Introducing HookAgent&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HookAgent&lt;/strong&gt; is a small companion CLI tool that ships with the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt;. It acts as the bridge between Claude Code’s hook system and the Coding Agent Explorer.&lt;/p&gt;
&lt;p&gt;The diagram below shows how HookAgent receives hook events from Claude Code and forwards them to the Coding Agent Explorer.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing how HookAgent receives hook events from Claude Code via stdin, returns an exit code, and forwards the event to the Coding Agent Explorer over HTTP&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;234&quot; src=&quot;https://nestenius.se/_astro/claude-code-hookagent-event-flow-diagram-1024x234.yCD1NgCJ_2jsxpd.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To use it, configure Claude Code to call HookAgent for each hook event you want to capture. From that point on, every time Claude Code fires a hook, it runs HookAgent and passes the event data via &lt;strong&gt;stdin&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;HookAgent’s core responsibility is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read the incoming event from &lt;strong&gt;stdin&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Attach Claude Code environment variables like &lt;strong&gt;CLAUDE_PROJECT_DIR&lt;/strong&gt; and &lt;strong&gt;CLAUDE_SESSION_ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Forward everything to the Coding Agent Explorer via a single HTTP POST request.&lt;/li&gt;
&lt;li&gt;When the Explorer receives a hook event, it updates the dashboard in real time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the Explorer is not running when a hook fires, HookAgent exits immediately and Claude Code continues normally. Observability is always optional.&lt;/p&gt;
&lt;h3 id=&quot;fact-stdin-and-exit-codes&quot;&gt;FACT: stdin and exit codes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;stdin&lt;/strong&gt; (standard input) is a standard way for one process to send data to another; in this case, Claude Code passes the hook event JSON as text to HookAgent via stdin.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;exit code&lt;/strong&gt; is how a process reports the result back: &lt;strong&gt;0&lt;/strong&gt; (zero) means success, while any non-zero value signals an error and can be used to block an action.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-hookagent&quot;&gt;Getting Started with HookAgent&lt;/h2&gt;
&lt;p&gt;Getting hooks running takes about five minutes. Here is what you need.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before you get started, make sure you have the following installed and ready:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dot.net/&quot;&gt;.NET 10 SDK&lt;/a&gt; installed.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt; source code downloaded.&lt;/li&gt;
&lt;li&gt;Claude Code installed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Introducing the Coding Agent Explorer&lt;/a&gt; for installation and setup instructions.&lt;/p&gt;
&lt;h3 id=&quot;step-1-build-hookagent&quot;&gt;Step 1: Build HookAgent&lt;/h3&gt;
&lt;p&gt;Open a terminal in the root folder of the Coding Agent Explorer and run the appropriate publish script for your platform:&lt;/p&gt;
&lt;h4 id=&quot;windows&quot;&gt;Windows:&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;publish.bat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;macos--linux&quot;&gt;macOS / Linux:&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;bash&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; publish.sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This builds both the Coding Agent Explorer and HookAgent into the &lt;strong&gt;Published&lt;/strong&gt; folder:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Published/CodingAgentExplorer&lt;/strong&gt;&lt;br&gt;
The proxy and dashboard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Published/HookAgent/HookAgent.exe&lt;/strong&gt;&lt;br&gt;
HookAgent (Windows: .exe, macOS/Linux: no extension).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-2-create-a-working-directory&quot;&gt;Step 2: Create a working directory&lt;/h3&gt;
&lt;p&gt;Create a fresh folder where you will run claude (in this example, we name it &lt;strong&gt;MyApp&lt;/strong&gt;), then copy the &lt;strong&gt;HookAgent&lt;/strong&gt; folder from the &lt;strong&gt;Published&lt;/strong&gt; folder into it. After copying, your working directory should look like this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bat&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;C:\MyApp\HookAgent\HookAgent.exe&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;~&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/MyApp/HookAgent/HookAgent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-configure-claude-code-hooks&quot;&gt;Step 3: Configure Claude Code hooks&lt;/h3&gt;
&lt;p&gt;The Coding Agent Explorer includes ready-to-use sample &lt;strong&gt;settings.json&lt;/strong&gt; files for each platform.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;strong&gt;.claude&lt;/strong&gt; folder in your working directory (&lt;strong&gt;MyApp&lt;/strong&gt;) if it does not already exist.&lt;/li&gt;
&lt;li&gt;Copy the sample &lt;strong&gt;settings.json&lt;/strong&gt; for your platform into that &lt;strong&gt;.claude&lt;/strong&gt; folder.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use one of these sample files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows: &lt;strong&gt;HookAgentSample-Settings-Windowssettings.json&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;macOS / Linux: &lt;strong&gt;HookAgent/Sample-Settings-LinuxMacOS/settings.json&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your working directory should now look like this:&lt;br&gt;
&lt;strong&gt;Windows:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C:\MyApp\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      HookAgent\HookAgent.exe&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     .claude\settings.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;~/MyApp/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    HookAgent/HookAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    .claude/settings.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This registers &lt;strong&gt;HookAgent&lt;/strong&gt; for all Claude Code hook events. Feel free to remove any events you do not need. The full list of supported events and the &lt;strong&gt;settings.json&lt;/strong&gt; syntax is documented in the &lt;a href=&quot;https://code.claude.com/docs/en/hooks&quot;&gt;Claude Code hooks reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;“matcher”: ”.*”&lt;/strong&gt; field is a regex pattern applied to the tool name. &lt;strong&gt;.*&lt;/strong&gt; matches all tools, so the hook fires for every tool call under that event. You can narrow it down. For example, &lt;strong&gt;“matcher”: “Bash”&lt;/strong&gt; would only fire for Bash tool calls. Not all events support a matcher. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Claude Code runs hook commands through bash on all platforms, including Windows. Always use forward slashes in the command path. The Windows sample uses &lt;strong&gt;HookAgent/HookAgent.exe&lt;/strong&gt;. The macOS/Linux sample uses &lt;strong&gt;HookAgent/HookAgent&lt;/strong&gt; with no .exe extension.&lt;/p&gt;
&lt;h3 id=&quot;step-4-verify-the-hooks-are-registered&quot;&gt;Step 4: Verify the hooks are registered&lt;/h3&gt;
&lt;p&gt;Start Claude Code from your working directory and run the &lt;strong&gt;/hooks&lt;/strong&gt; command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/hooks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Claude Code will list every configured hook event and the command registered for each one. You should see all hook events pointing to HookAgent. If the list is empty or events are missing, check that &lt;strong&gt;.claude/settings.json&lt;/strong&gt; is in the right folder and that the JSON is valid.&lt;/p&gt;
&lt;p&gt;Once you have confirmed the hooks are registered, exit Claude Code and move on to the next step.&lt;/p&gt;
&lt;h3 id=&quot;step-5-start-the-coding-agent-explorer&quot;&gt;Step 5: Start the Coding Agent Explorer&lt;/h3&gt;
&lt;p&gt;From the Coding Agent Explorer folder, start the Explorer:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Windows the browser opens automatically. On macOS and Linux, open the dashboard manually at &lt;code&gt;https://localhost:5001&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Don’t forget to enable the proxy in Claude Code as described in the previous post in this series.&lt;/p&gt;
&lt;h3 id=&quot;step-6-test-hookagent&quot;&gt;Step 6: Test HookAgent&lt;/h3&gt;
&lt;p&gt;With the Explorer running, test &lt;strong&gt;HookAgent&lt;/strong&gt; from your working directory before starting a full Claude Code session:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;hook_event_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;UserPromptSubmit&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;session_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&apos; | HookAgentHookAgent.exe&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Windows (cmd):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bat&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;hook_event_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;UserPromptSubmit&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;session_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;} &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HookAgentHookAgent.exe&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{&quot;hook_event_name&quot;:&quot;UserPromptSubmit&quot;,&quot;session_id&quot;:&quot;test&quot;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; |&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HookAgent/HookAgent&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A &lt;strong&gt;UserPromptSubmit&lt;/strong&gt; event should appear in the &lt;strong&gt;Conversation&lt;/strong&gt; &lt;strong&gt;View&lt;/strong&gt; immediately. Make sure the “&lt;strong&gt;Hook Events&lt;/strong&gt;” checkbox in the Conversation View is checked. If the Explorer is not running, the command exits silently and Claude Code is never affected.&lt;/p&gt;
&lt;h3 id=&quot;step-7-run-claude&quot;&gt;Step 7: Run Claude&lt;/h3&gt;
&lt;p&gt;From your working directory (&lt;strong&gt;C:MyApp&lt;/strong&gt; on Windows, &lt;strong&gt;~/MyApp&lt;/strong&gt; on macOS/Linux), run:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;claude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Hook events&lt;/strong&gt; will start appearing in the &lt;strong&gt;Conversation View&lt;/strong&gt; as soon as Claude Code starts up, and then for every action it takes. Make sure the “Hook Events” checkbox in the Conversation View is checked, otherwise hook events will be hidden.&lt;/p&gt;
&lt;h2 id=&quot;seeing-claude-code-hooks-and-tool-calls-in-real-time&quot;&gt;Seeing Claude Code Hooks and Tool Calls in Real Time&lt;/h2&gt;
&lt;p&gt;Once everything is running, the Conversation View becomes a live timeline of everything Claude Code does, with hook events interleaved alongside the API calls. You get a complete picture that was previously invisible.&lt;/p&gt;
&lt;p&gt;Here is what a typical interaction looks like on the timeline when you ask Claude Code to fix a bug:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:01 SessionStart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:03 UserPromptSubmit &quot;Fix the null reference exception in UserService.cs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:03 POST /v1/messages (Claude thinks about what to do)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:05 PreToolUse Read (about to read UserService.cs)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:05 PostToolUse Read (file contents returned)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:05 POST /v1/messages (Claude analyses the code)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:08 PreToolUse Edit (about to write the fix)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:08 PostToolUse Edit (file updated)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:08 POST /v1/messages (Claude confirms the fix)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;13:42:10 Stop&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In under 10 seconds, Claude Code made 3 LLM calls and 2 tool calls. Without hooks, you only see the API requests. With hooks, you see the full story.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing how Claude Code sends hook event JSON to HookAgent via stdin, receives an exit code response, and forwards the event to the Coding Agent Explorer over HTTP&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;877&quot; height=&quot;768&quot; src=&quot;https://nestenius.se/_astro/claude-code-hookagent-stdin-exit-code-flow.DAXbSmiZ_Z1jp3Yp.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Clicking any hook event in the timeline opens the full JSON payload. For a &lt;strong&gt;PreToolUse&lt;/strong&gt; event, for example, you see the tool name, the exact input parameters, the session ID, and the working directory. For a &lt;strong&gt;PermissionRequest&lt;/strong&gt;, you see exactly what Claude Code wants to do and why.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;session_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;814cc648-5d90-44e2-9239-144ad62abc76&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;transcript_path&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;C:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Users&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Tore&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.claude&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;projects&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;C--Conf&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;814cc648-5d90-44e2-9239-144ad62abc76.jsonl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;cwd&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;C:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;\\&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Conf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;permission_mode&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;default&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;hook_event_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;PreToolUse&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tool_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Glob&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tool_input&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;pattern&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;**/UserService.cs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tool_use_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;toolu_019GP34HeFh8N4QkWQWdAyUA&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;why-this-is-useful-in-workshops&quot;&gt;Why This Is Useful in Workshops&lt;/h2&gt;
&lt;p&gt;I use the hooks feature extensively in my workshops on agentic development. The question I hear most often from developers who are new to coding agents is:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;But what is it actually doing? And what data is actually passed to my hooks?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Conversation View with hooks answers that question directly. You can project the dashboard on a shared screen and watch every session event, tool call, and permission request appear in real time as Claude Code works. It turns the agent from a magic black box into a system that developers can reason about, question, and understand.&lt;/p&gt;
&lt;p&gt;In my experience, once developers see the full picture, a lot of things click. They understand why the agent reads files before editing them. They see how it loops back to the LLM after each tool call. They notice where token costs accumulate. And they start writing better prompts as a result.&lt;/p&gt;
&lt;p&gt;For more details about my workshops and training classes, visit &lt;a href=&quot;https://tn-data.se/training/&quot;&gt;tn-data.se/training&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;claude-code-hook-events-reference&quot;&gt;Claude Code Hook Events Reference&lt;/h2&gt;
&lt;p&gt;As of today, Claude Code supports these hook events. For the full and up-to-date reference, see the &lt;a href=&quot;https://code.claude.com/docs/en/hooks&quot;&gt;Claude Code hooks documentation&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SessionStart&lt;/strong&gt;&lt;br&gt;
Session begins or resumes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UserPromptSubmit&lt;/strong&gt;&lt;br&gt;
User submits a prompt&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PreToolUse&lt;/strong&gt;&lt;br&gt;
Fires before any tool executes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostToolUse&lt;/strong&gt;&lt;br&gt;
Fires after a tool succeeds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostToolUseFailure&lt;/strong&gt;&lt;br&gt;
Fires after a tool fails&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PermissionRequest&lt;/strong&gt;&lt;br&gt;
Claude Code requests permission to perform an action&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stop&lt;/strong&gt;&lt;br&gt;
Claude finishes responding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SubagentStart&lt;/strong&gt;&lt;br&gt;
A subagent is started&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SubagentStop&lt;/strong&gt;&lt;br&gt;
A subagent finishes execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notification&lt;/strong&gt;&lt;br&gt;
Claude Code sends a notification&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PreCompact&lt;/strong&gt;&lt;br&gt;
Fires before context compaction&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ConfigChange&lt;/strong&gt;&lt;br&gt;
A settings file changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TeammateIdle&lt;/strong&gt;&lt;br&gt;
Agent team coordination event&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TaskCompleted&lt;/strong&gt;&lt;br&gt;
A task is marked as complete&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SessionEnd&lt;/strong&gt;&lt;br&gt;
Session terminates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;PreToolUse&lt;/strong&gt; and &lt;strong&gt;PostToolUse&lt;/strong&gt; events are the most useful in practice. They fire for every tool call, whether it is a file read, a bash command, a web fetch, or an MCP tool invocation. Together, they give you a complete record of every action the agent takes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PreToolUse&lt;/strong&gt; is where you enforce rules: block access to sensitive files, reject dangerous shell commands, or log what the agent is about to do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostToolUse&lt;/strong&gt; fires after a tool succeeds and is where you react to what just happened. When a tool fails, PostToolUseFailure fires instead and provides the error details.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Common uses include:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run your test suite automatically after the agent edits a file&lt;/li&gt;
&lt;li&gt;Trigger a linter or code formatter after a write&lt;/li&gt;
&lt;li&gt;Log the completed action to an audit trail&lt;/li&gt;
&lt;li&gt;Notify an external system (Slack, CI pipeline) that a file changed&lt;/li&gt;
&lt;li&gt; Inspect the tool output before the agent continue&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;whats-next-inspecting-claude-code-with-mcp-observer&quot;&gt;What’s Next: Inspecting Claude Code with MCP Observer&lt;/h2&gt;
&lt;p&gt;This post covered the hooks feature. The next post in this series will look at the &lt;strong&gt;MCP Observer&lt;/strong&gt;, a new feature that lets you intercept and inspect traffic between Claude Code and any Model Context Protocol (MCP) server. If you’ve ever wanted to understand how &lt;strong&gt;MCP tools&lt;/strong&gt; like &lt;strong&gt;Microsoft Learn&lt;/strong&gt; or &lt;strong&gt;Context7&lt;/strong&gt; actually work under the hood when Claude Code uses them, that post is for you.&lt;/p&gt;
&lt;p&gt;In the meantime, the full project is open-source and available on GitHub: &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;github.com/tndata/CodingAgentExplorer&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;i-want-your-feedback&quot;&gt;I Want Your Feedback&lt;/h2&gt;
&lt;p&gt;The Coding Agent Explorer is open-source and I want to improve it with your help. If you find a bug, have a feature request, or want to share your experience, please create an &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer/issues&quot;&gt;issue on GitHub&lt;/a&gt;. I read every issue and appreciate all feedback.&lt;/p&gt;
&lt;p&gt;Contributions are also very welcome. The codebase is intentionally kept simple (a single NuGet dependency, a vanilla JS frontend) to make it easy for anyone to jump in.&lt;/p&gt;
&lt;h2 id=&quot;want-to-learn-agentic-development&quot;&gt;Want to Learn Agentic Development?&lt;/h2&gt;
&lt;p&gt;If this topic interests you, I’d love to help you go deeper. I’m currently working on a workshop called &lt;strong&gt;Agentic Development with Claude Code&lt;/strong&gt;, where we explore how coding agents work, how to use them effectively, and how to build workflows around them. The Coding Agent Explorer is one of the tools I to use to help participants see what is really happening behind the scenes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Learn more about my AI and Claude Code workshops and courses: &lt;a href=&quot;https://tn-data.se/courses&quot;&gt;Agentic Development and AI workshops&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I also give a presentation called &lt;a href=&quot;https://tn-data.se/talks/ai-development/&quot;&gt;How Does a Coding Agent Work?&lt;/a&gt; that covers the architecture and inner workings of AI coding agents. &lt;a href=&quot;https://tn-data.se/&quot;&gt;Contact me&lt;/a&gt; if you’d like me to run this at your company or conference.&lt;/p&gt;
&lt;h2 id=&quot;claude-code-hooks-faq&quot;&gt;Claude Code Hooks FAQ&lt;/h2&gt;
&lt;p&gt;Here are answers to common questions about how hooks work in Claude Code, how to configure them, and what to expect in practice.&lt;/p&gt;
&lt;h3 id=&quot;does-hookagent-slow-down-claude-code&quot;&gt;Does HookAgent slow down Claude Code?&lt;/h3&gt;
&lt;p&gt;No. HookAgent makes a single HTTP POST per event and exits. The call is fast, and if the dashboard is not running it returns immediately. In practice, you will not notice any difference in Claude Code’s responsiveness.&lt;/p&gt;
&lt;h3 id=&quot;do-i-need-to-change-my-project-or-code-to-use-hooks&quot;&gt;Do I need to change my project or code to use hooks?&lt;/h3&gt;
&lt;p&gt;No. You only need to create or update the &lt;strong&gt;.claude/settings.json&lt;/strong&gt; file in the directory where you run claude. Nothing in your project code changes.&lt;/p&gt;
&lt;h3 id=&quot;can-i-use-only-some-of-the-hook-events&quot;&gt;Can I use only some of the hook events?&lt;/h3&gt;
&lt;p&gt;Yes. You can remove any hook entries from &lt;strong&gt;settings.json&lt;/strong&gt; that you don’t want. For example, if you only care about tool calls, keep PreToolUse and PostToolUse and remove the rest.&lt;/p&gt;
&lt;h3 id=&quot;does-this-work-on-macos-and-linux&quot;&gt;Does this work on macOS and Linux?&lt;/h3&gt;
&lt;p&gt;Yes. Use &lt;strong&gt;publish.bat&lt;/strong&gt; on Windows or bash &lt;strong&gt;publish.sh&lt;/strong&gt; on macOS and Linux. Each script detects the current platform and builds a single-file HookAgent executable for it. On Linux and macOS the executable has no .exe extension, so use &lt;strong&gt;HookAgent/HookAgent&lt;/strong&gt; as the command path in your &lt;strong&gt;settings.json&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;is-this-tool-suitable-for-production-use&quot;&gt;Is this tool suitable for production use?&lt;/h3&gt;
&lt;p&gt;No. The Coding Agent Explorer is designed as a development and teaching tool. It is not intended for production use.&lt;/p&gt;
&lt;h3 id=&quot;can-i-block-all-sensitive-file-access-using-pretooluse&quot;&gt;Can I block all sensitive file access using PreToolUse?&lt;/h3&gt;
&lt;p&gt;Not completely. PreToolUse lets you block &lt;strong&gt;direct&lt;/strong&gt; tool calls (like reading &lt;strong&gt;secret.key&lt;/strong&gt;), but it does not provide a true sandbox.&lt;/p&gt;
&lt;p&gt;For example, the model might:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate a script (e.g. Bash or PowerShell) that reads files indirectly&lt;/li&gt;
&lt;li&gt;Request access to a broader directory (e.g. “load all files in this folder”)&lt;/li&gt;
&lt;li&gt;Use other tools or workflows that bypass your specific path checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you give Claude Code &lt;strong&gt;shell access&lt;/strong&gt; (Bash, command line, PowerShell, etc.), it can effectively do anything you can do within that environment; including reading sensitive files through indirect means.&lt;/p&gt;
&lt;p&gt;Even for direct tool calls, your hook logic must be implemented carefully. Naive checks (for example simple string matching on file paths) can sometimes be bypassed using variations in path representation, encoding, or traversal patterns. In practice, you need to normalize and validate inputs rigorously.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In other words, you are filtering requests, not enforcing OS-level isolation.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-does-it-take-to-truly-secure-file-access&quot;&gt;What does it take to truly secure file access?&lt;/h3&gt;
&lt;p&gt;If you need strong guarantees, you need to move beyond hooks and enforce restrictions at the environment level:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Run Claude Code in a sandbox&lt;/strong&gt;&lt;br&gt;
(container, VM, or restricted workspace)&lt;/li&gt;
&lt;li&gt;**Limit filesystem permissions&lt;br&gt;
**(only expose allowed directories)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disable or tightly restrict shell access&lt;/strong&gt;&lt;br&gt;
(for example by disabling the Bash tool or restricting what commands can be executed, since it can be used to invoke other interpreters like PowerShell or cmd)&lt;/li&gt;
&lt;li&gt;**Use allowlists instead of blocklists&lt;br&gt;
**(explicitly permit safe paths only)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hooks are best used as a &lt;strong&gt;guardrail and observability layer&lt;/strong&gt;, not as your primary security boundary.&lt;/p&gt;
&lt;h2 id=&quot;links-to-other-blog-posts&quot;&gt;Links to other blog posts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Introducing the Coding Agent Explorer (.NET)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Implementing BFF Pattern in ASP.NET Core for SPAs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/introducing-the-data-protection-api-key-ring-debugger/&quot;&gt;Introducing the Data Protection API Key Ring Debugger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>ai</category><category>net</category></item><item><title>Introducing the Coding Agent Explorer (.NET)</title><link>https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/</link><guid isPermaLink="true">https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/</guid><description>I&apos;m excited to introduce you to the Coding Agent Explorer, a new open-source .NET teaching tool I&apos;ve created that lets you see exactly what happens under</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’m excited to introduce you to the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt;, a new open-source .NET teaching tool I’ve created that lets you see exactly what happens under the hood when an AI coding agent works on your code.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How claude Code and Coding Agent Explorer works together&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1007&quot; height=&quot;247&quot; src=&quot;https://nestenius.se/_astro/Claude-Code-With-The-Coding-Agent-Explorer.B0n9SfZu_bGCil.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It currently supports &lt;a href=&quot;https://claude.com/product/claude-code&quot;&gt;Claude Code&lt;/a&gt; (with more agents on the roadmap), and it is designed to help developers understand and adopt agentic development. If you’ve ever wondered what’s really going on between your prompts and the code changes that magically appear, this tool lets you see inside the process.&lt;/p&gt;
&lt;p&gt;This article is part of a multi-part series on the Coding Agent Explorer, an open-source .NET tool for understanding what AI coding agents do under the hood.&lt;/p&gt;
&lt;h3 id=&quot;coding-agent-explorer-series&quot;&gt;Coding Agent Explorer Series&lt;/h3&gt;
&lt;p&gt;If you’re new to the tool, start with Part 1. Otherwise, jump to the section that interests you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 1 – Introducing the Coding Agent Explorer (.NET)&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/exploring-claude-code-hooks-with-the-coding-agent-explorer-net/&quot;&gt;Part 2 – Exploring Claude Code Hooks with the Coding Agent Explorer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 3 – Visualizing Claude Code MCP Requests with the Coding Agent Explorer (coming soon)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;heres-what-youll-learn&quot;&gt;Here’s what you’ll learn:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Why understanding coding agents matters for every developer&lt;/li&gt;
&lt;li&gt;What the Coding Agent Explorer does and how it works&lt;/li&gt;
&lt;li&gt;How to get started in just a few minutes&lt;/li&gt;
&lt;li&gt;What’s on the roadmap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s dive in!&lt;/p&gt;
&lt;h2 id=&quot;what-is-agentic-development&quot;&gt;What Is Agentic Development?&lt;/h2&gt;
&lt;p&gt;Before we dive into the tool itself, let’s talk about what agentic development means. In traditional AI-assisted coding, you might get autocomplete suggestions or ask a chatbot a question and then copy the answer into your code. But agentic development takes this much further.&lt;/p&gt;
&lt;p&gt;In agentic development, an AI agent operates autonomously inside your development environment. It can read your files, search your codebase, run commands, edit code, and even verify its own changes.&lt;/p&gt;
&lt;p&gt;Instead of just suggesting code to you, the agent acts on your behalf, working through multi-step tasks in a loop: it thinks about what to do, takes an action, observes the result, and then decides what to do next.&lt;/p&gt;
&lt;p&gt;Several tools have emerged in this space:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://code.claude.com/docs&quot;&gt;Claude Code&lt;/a&gt; by Anthropic, a CLI-based coding agent&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/features/copilot&quot;&gt;GitHub Copilot&lt;/a&gt;, an AI development assistant&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cursor.com/&quot;&gt;Cursor&lt;/a&gt;, an AI-first code editor with agentic features&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://windsurf.com/&quot;&gt;Windsurf&lt;/a&gt;, another AI-powered editor&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lovable.dev/&quot;&gt;Lovable&lt;/a&gt;, focused on AI-driven app generation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tools are rapidly changing how developers write software. But with all this autonomy, it becomes even more important to understand what the agent is actually doing! That’s where the &lt;strong&gt;Coding Agent Explorer&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;h2 id=&quot;why-i-built-this-tool&quot;&gt;Why I Built This Tool&lt;/h2&gt;
&lt;p&gt;In my role as instructor and creating &lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;training material&lt;/a&gt;, I often create hands-on tools to help participants better understand, visualize, and grasp the various concepts that I teach.&lt;/p&gt;
&lt;p&gt;The goal is that all participants should have a solid mental model of how things actually work under the hood. This is the same approach I took with the &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;CloudDebugger&lt;/a&gt;, an open-source tool I created for teaching &lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Azure to developers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With AI coding agents becoming part of the mainstream developer toolkit, I saw the same need. For most of us, the experience feels like a black box: you type a prompt, something happens behind the scenes, and code appears.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But what is really going on?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wanted a tool that lets you peek inside that black box and see every API call, every tool invocation, and every decision the agent makes. This tool helps you answer questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How are &lt;strong&gt;tools&lt;/strong&gt; and &lt;strong&gt;MCP servers&lt;/strong&gt; used by the agent?&lt;/li&gt;
&lt;li&gt;What does the &lt;strong&gt;/init&lt;/strong&gt; command actually do?&lt;/li&gt;
&lt;li&gt;What happens when I enable &lt;strong&gt;plan mode&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;How many &lt;strong&gt;tokens&lt;/strong&gt; do my tools and &lt;strong&gt;system prompt&lt;/strong&gt; consume?&lt;/li&gt;
&lt;li&gt;How can an agent &lt;strong&gt;read&lt;/strong&gt; and &lt;strong&gt;modify&lt;/strong&gt; my files?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;What Happens When I Use Claude Code&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;770&quot; height=&quot;203&quot; src=&quot;https://nestenius.se/_astro/What-Happens-When-I-Use-Claude-Code.CMmJOCgM_Z1sOgLW.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;With insight into the agent’s decisions, you can refine your prompts, reduce unnecessary tool calls, and reduce token costs.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-coding-agent-explorer&quot;&gt;What Is the Coding Agent Explorer?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt; is a .NET &lt;strong&gt;reverse proxy&lt;/strong&gt; combined with a real-time web dashboard. It sits between your coding agent (currently Claude Code) and the Anthropic API, intercepting every request and response that flows between them. Everything it captures is displayed in a live dashboard that you can explore while the agent is working or after the work is complete.&lt;/p&gt;
&lt;p&gt;Here’s how the architecture looks at a high level:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Architecture diagram of Coding Agent Explorer showing Claude Code connecting via ports 8888 and 5001 to the .NET proxy, which&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;970&quot; height=&quot;413&quot; src=&quot;https://nestenius.se/_astro/Coding-Agent-Explorer-Proxy-For-Claude-Code.0SCOJOXu_1uFeS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The proxy captures all API traffic and streams it to the dashboard using SignalR for real-time updates. You see everything as it happens with no delay.&lt;/p&gt;
&lt;p&gt;On the technology side, the project is built with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;.NET 10&lt;/strong&gt; and &lt;strong&gt;ASP.NET Core&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dotnet.github.io/yarp/index.html&quot;&gt;&lt;strong&gt;YARP&lt;/strong&gt;&lt;/a&gt; (Yet Another Reverse Proxy) for the proxy layer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction&quot;&gt;SignalR&lt;/a&gt;&lt;/strong&gt; for real-time communication between the proxy and the dashboard&lt;/li&gt;
&lt;li&gt;A vanilla &lt;strong&gt;HTML/JS/CSS&lt;/strong&gt; frontend&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;three-ways-to-explore-with-coding-agent&quot;&gt;Three Ways to Explore With Coding Agent&lt;/h2&gt;
&lt;p&gt;The dashboard provides thre complementary views for exploring what the coding agent is doing. Each gives you a different perspective on the same data. The &lt;strong&gt;MCP Observer&lt;/strong&gt; will be covered in a separate blog post.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Coding Agent Explorer for Claude code Main Menu, showing the HTTP Inspector, Conversation view and the MCP Observer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;458&quot; src=&quot;https://nestenius.se/_astro/Coding_Agent_Explorer_Main_Page_With_MCP-1024x458.Cusep5nH_29C5mL.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-http-inspector&quot;&gt;The HTTP Inspector&lt;/h2&gt;
&lt;p&gt;The HTTP Inspector is the raw, detailed view. It shows every API call in a table with the key information at a glance: timestamp, HTTP method, model used, token counts, and timing.&lt;/p&gt;
&lt;p&gt;You can click on any row to inspect the full details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Request and response headers&lt;/strong&gt; in their entirety&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request and response bodies&lt;/strong&gt;, formatted for easy reading&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time response data&lt;/strong&gt;, so you can see the full response as it was received by the agent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token usage breakdown&lt;/strong&gt;: input tokens, output tokens, cache creation tokens, and cache read tokens&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance metrics&lt;/strong&gt;: total duration and time-to-first-token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This view is especially valuable when you want to understand the technical details of the API communication. You can see exactly what the agent sends to the model, what the model responds with, and how long each step takes.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Http Inspector tool in Claude Code Coding Explorer Tool&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;690&quot; src=&quot;https://nestenius.se/_astro/coding-agent-explorer-http-inspector-claude-api-requests.B9u8j7cH_Z1fSYsr.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then, you can click on a given request and view all the request and response details:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Coding Agent Explorer HTTP Inspector showing POST requests to Claude API with token usage and timing details&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;690&quot; src=&quot;https://nestenius.se/_astro/coding-agent-explorer-http-inspector-claude-api-requests-2.Dt4xTegd_Z1EY5qe.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, this view is useful, but not really helpful when you’re trying to understand how an agent works. This is why we also added the &lt;strong&gt;Conversation View&lt;/strong&gt;, which allows you to make sense of all of these requests.&lt;/p&gt;
&lt;h2 id=&quot;the-conversation-view&quot;&gt;The Conversation View&lt;/h2&gt;
&lt;p&gt;The Conversation View takes the same API data and renders it in a chat-style format that’s easier to follow. Instead of raw HTTP traffic, you see the conversation as it unfolds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;System prompts&lt;/strong&gt; that set up the agent’s behavior&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User messages&lt;/strong&gt; (your prompts)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assistant responses&lt;/strong&gt; with the agent’s reasoning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool calls&lt;/strong&gt; like Read, Write, Bash, Grep, and others, showing exactly what the agent does&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool results&lt;/strong&gt; showing what came back from each tool invocation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP tool usage&lt;/strong&gt;, letting you explore how the agent presents and invokes CP servers when communicating with the LLM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Large content sections are also collapsible, so you can focus on the parts that interest you. Each message also provides token and character statistics, giving you a sense of how much context each part of the conversation consumes.&lt;/p&gt;
&lt;p&gt;This is the view I use the most in my workshops. It gives you a clear picture of how the agent “thinks”. That shows how it breaks down a problem, which tools it decides to use, and how it iterates toward a solution.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Claude Code Converstation example from the Coding Assistant Explorer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;690&quot; src=&quot;https://nestenius.se/_astro/Coding_Agent_Explorer_Conversation_View-1024x690.Dyfm-uoG_ZAfqVq.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-can-you-learn-from-it&quot;&gt;What Can You Learn From It?&lt;/h2&gt;
&lt;p&gt;Once you start monitoring the agent’s API calls, you’ll discover many things that aren’t obvious from the outside. Here are some of the insights that the Coding Agent Explorer reveals to us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How prompts are constructed.&lt;/strong&gt;&lt;br&gt;
You’ll see the actual system prompts that Claude Code sends to the model. These are far more detailed than you might expect. Impressively, you’ll get to see instructions about tool usage, safety, code style, and more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool usage patterns.&lt;/strong&gt;&lt;br&gt;
You’ll also see how the model selects and invokes tools like &lt;strong&gt;Read&lt;/strong&gt;, &lt;strong&gt;Write&lt;/strong&gt;, &lt;strong&gt;Bash&lt;/strong&gt;, &lt;strong&gt;Grep&lt;/strong&gt;, and &lt;strong&gt;Glob&lt;/strong&gt;. You’ll notice how it reads files before editing them, how it searches for code, and how it verifies its changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token economics.&lt;/strong&gt;&lt;br&gt;
It reveals what costs tokens and how prompt caching works. You’ll see cache creation tokens (when the model stores a prompt prefix), and cache read tokens (when it reuses a cached prefix). This is key for understanding performance and cost with these agents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The agentic conversation loop.&lt;/strong&gt;&lt;br&gt;
Another cool feature is seeing how the back-and-forth between the agent and the API works. Each “turn” involves the model generating a response (potentially with tool calls), the agent executing those tools, and then sending the results back to the model. This loop continues until the task is complete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real-time observation.&lt;/strong&gt;&lt;br&gt;
You’ll be able to see the entire conversation unfold in real time as the agent works, giving you immediate insight into what it’s doing and why.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-model execution&lt;/strong&gt;&lt;br&gt;
Observe how tasks are distributed between models such as Haiku and Opus.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have you wondered why Claude Code sometimes seems to “&lt;em&gt;&lt;strong&gt;think&lt;/strong&gt;&lt;/em&gt;” for a while before responding? The Conversation View shows you exactly what’s happening: the agent might be reading multiple files, searching for patterns, or analyzing code before it starts writing its response to you.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Getting the Coding Agent Explorer running doesn’t take as long as you might think; it takes just a few minutes. Here’s the quick version (the &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;GitHub README&lt;/a&gt; has full details):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; You’ll need the &lt;a href=&quot;https://dotnet.microsoft.com/en-us/download&quot;&gt;.NET 10 SDK&lt;/a&gt; installed.&lt;/p&gt;
&lt;h3 id=&quot;1-clone-and-run-the-project&quot;&gt;1. Clone and run the project:&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://github.com/tndata/CodingAgentExplorer.git&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cd&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; CodingAgentExplorer&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; dotnet&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This starts three endpoints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The reverse proxy on port &lt;strong&gt;8888&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The web dashboard on ports &lt;strong&gt;5000&lt;/strong&gt; (HTTP) and &lt;strong&gt;5001&lt;/strong&gt; (HTTPS).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The dashboard opens automatically in your browser.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Coding Agent Explorer architecture diagram showing reverse proxy on port 8888 and dashboard on ports 5000 and 5001&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;659&quot; height=&quot;274&quot; src=&quot;https://nestenius.se/_astro/Coding_Agent_Explorer_Http_Ports.DKkqzApb_ZxC5mH.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;2-point-claude-code-at-the-proxy&quot;&gt;&lt;strong&gt;2. Point Claude Code at the proxy:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;On Windows (cmd):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bat&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;http://localhost:8888&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Windows (PowerShell):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$env:ANTHROPIC_BASE_URL = &quot;http://localhost:8888&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The repository also includes &lt;strong&gt;EnableProxy.bat&lt;/strong&gt; and &lt;strong&gt;DisableProxy.bat&lt;/strong&gt; scripts for convenience. These only affect the current terminal session, so closing the terminal automatically clears the setting.&lt;/p&gt;
&lt;h3 id=&quot;3-use-claude-code-as-normal&quot;&gt;3. Use Claude Code as normal.&lt;/h3&gt;
&lt;p&gt;Every API call will now flow through the proxy, and you’ll see it appear in the dashboard in real time.&lt;/p&gt;
&lt;p&gt;Note: The proxy only listens on &lt;strong&gt;localhost&lt;/strong&gt; and is not exposed to the network. API keys are automatically redacted from the stored data. Request data is kept in memory only (up to 1,000 requests), with no persistence to disk.&lt;/p&gt;
&lt;h2 id=&quot;its-time-to-understand-how-coding-agents-work&quot;&gt;It’s Time to Understand How Coding Agents Work&lt;/h2&gt;
&lt;p&gt;AI coding agents are becoming a real part of the daily developer workflow. More and more developers and teams are integrating them into how they write, review, and maintain code.&lt;/p&gt;
&lt;p&gt;As .NET developers, we have a tradition of wanting to understand the tools we use, and we’ll ultimately need to bring the same curiosity and rigor into understanding how AI coding agents work, too.&lt;/p&gt;
&lt;p&gt;For example, while writing the blog post &lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredential Under the Hood&lt;/a&gt;, I cloned the &lt;a href=&quot;https://github.com/Azure/azure-sdk-for-net&quot;&gt;Azure SDK for .NET repository&lt;/a&gt; and patched the code locally to better understand how the token credential requests access tokens from Azure. That hands-on exploration gave me insights that go beyond what the documentation alone can provide.&lt;/p&gt;
&lt;p&gt;For example, we can examine in detail how Claude Code uses its tools:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of how claude code uses the tools in Coding Agent Explorer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;731&quot; height=&quot;1024&quot; src=&quot;https://nestenius.se/_astro/Claude-Code-Tool-Usage-Internals-In-Coding-Agent-Explorer-731x1024.BehCssnR_Z1v8Nf3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In my workshops, I often say, “&lt;strong&gt;The things you’re scared of, you should do more often&lt;/strong&gt;.”&lt;/p&gt;
&lt;p&gt;If coding agents feel like magic or a mystery, that’s exactly why you should look under the hood. Understanding what the agent does (and doesn’t do) helps you write better prompts, catch mistakes earlier, and build trust in the tool.&lt;/p&gt;
&lt;h2 id=&quot;i-want-your-feedback&quot;&gt;I Want Your Feedback!&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer&quot;&gt;Coding Agent Explorer&lt;/a&gt; is open-source, and I want to improve it with your help. If you find a bug, have a feature request, or want to share your experience with the tool, please create an issue on GitHub. I read every issue and appreciate all feedback.&lt;/p&gt;
&lt;h3 id=&quot;contributions-are-also-welcome&quot;&gt;&lt;strong&gt;Contributions are also welcome!&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The codebase is intentionally kept simple (single NuGet dependency, vanilla frontend) to make it easy for anyone to jump in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fun fact:&lt;/strong&gt; The first pull request arrived four days after I published the tool, before I had even announced it.&lt;/p&gt;
&lt;p&gt;Currently, the tool only supports &lt;strong&gt;Claude Code&lt;/strong&gt; with the Anthropic API. Support for other coding agents and API providers is on the roadmap. If there’s a specific agent you’d like to see supported, let me know in the issues. Your input helps me prioritize what to build next.&lt;/p&gt;
&lt;h2 id=&quot;want-to-learn-agentic-development&quot;&gt;Want to Learn Agentic Development?&lt;/h2&gt;
&lt;p&gt;If this topic interests you, I’d love to help you go deeper. I am working on a new workshop called “&lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;&lt;strong&gt;Agentic Development with Claude Code&lt;/strong&gt;&lt;/a&gt;”, where we explore how coding agents work, how to use them effectively, and how to build workflows around them. The Coding Agent Explorer is one of the tools that I use in the workshop to help participants see what’s really happening behind the scenes.&lt;/p&gt;
&lt;p&gt;You can read more about the Agentic Development workshop and other development courses here: &lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;Agentic Development &amp;#x26; Development Workshops&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also have a presentation called “&lt;strong&gt;How Does a Coding Agent Work?&lt;/strong&gt;” that covers the architecture and inner workings of AI coding agents. Contact me if you’d like me to run this presentation at your company or conference.&lt;/p&gt;
&lt;p&gt;More information about my AI development talks is available here: &lt;a href=&quot;https://tn-data.se/talks&quot;&gt;AI Development Talks &amp;#x26; Presentations&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h2&gt;
&lt;p&gt;If you’re considering using the Coding Agent Explorer with Claude Code, these are the most common questions about setup, security, performance, and intended use.&lt;/p&gt;
&lt;h3 id=&quot;does-this-tool-work-with-coding-agents-other-than-claude-code&quot;&gt;Does this tool work with coding agents other than Claude Code?&lt;/h3&gt;
&lt;p&gt;Not yet. The Coding Agent Explorer currently only supports Claude Code via the Anthropic API. Support for other coding agents and API providers is on the roadmap. If there’s a specific agent you’d like to see supported, let me know on &lt;a href=&quot;https://github.com/tndata/CodingAgentExplorer/issues&quot;&gt;GitHub&lt;/a&gt;. &lt;/p&gt;
&lt;h3 id=&quot;does-the-proxy-affect-claude-codes-performance&quot;&gt;Does the proxy affect Claude Code’s performance?&lt;/h3&gt;
&lt;p&gt;The proxy adds minimal overhead. It captures traffic as it passes through, but does not modify or delay requests or responses in any meaningful way.&lt;/p&gt;
&lt;h3 id=&quot;is-my-api-key-safe&quot;&gt;Is my API key safe?&lt;/h3&gt;
&lt;p&gt;Yes. API keys (&lt;strong&gt;x-api-key&lt;/strong&gt; and &lt;strong&gt;Authorization headers&lt;/strong&gt;) are automatically redacted from all stored request data. The proxy only listens on localhost, so it is never exposed to the network. All captured data is kept in memory only and is lost when you stop the application.&lt;/p&gt;
&lt;h3 id=&quot;do-i-need-to-change-my-code-or-project-to-use-this&quot;&gt;Do I need to change my code or project to use this?&lt;/h3&gt;
&lt;p&gt;No. You only need to set a single environment variable (&lt;strong&gt;ANTHROPIC_BASE_URL&lt;/strong&gt;) to point Claude Code at the proxy. Everything else works exactly as before. When you’re done, just close the terminal or clear the variable.&lt;/p&gt;
&lt;h3 id=&quot;can-i-use-this-in-production&quot;&gt;Can I use this in production?&lt;/h3&gt;
&lt;p&gt;The Coding Agent Explorer is designed as a development and teaching tool. It is not intended for production use. Use it locally when you want to learn, debug, or demonstrate how a coding agent works.&lt;/p&gt;
&lt;h2 id=&quot;links-to-other-blog-posts&quot;&gt;Links to other blog posts&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/how-to-use-kurrentdb-for-event-sourcing-in-c-on-azure/&quot;&gt;How to Use KurrentDB for Event Sourcing in C# on Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;Exploring the Forwarded Headers Middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for AzureIntroducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/discovering-net-codebases-using-code-coverage-and-ncrunch/&quot;&gt;Discovering .NET codebases using code coverage and NCrunch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>ai</category><category>net</category><category>net</category></item><item><title>Duende IdentityServer 7: A Complete Setup Guide for ASP.NET Core</title><link>https://nestenius.se/net/duende-identityserver-7-a-complete-setup-guide-for-asp-net-core/</link><guid isPermaLink="true">https://nestenius.se/net/duende-identityserver-7-a-complete-setup-guide-for-asp-net-core/</guid><description>Duende IdentityServer is the leading OpenID Connect and OAuth 2 server for .NET. In this tutorial, I&apos;ll walk you through setting up Duende IdentityServer</description><pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt; is the leading OpenID Connect and OAuth 2 server for .NET. In this tutorial, I’ll walk you through setting up Duende IdentityServer 7.4.4 from scratch using ASP.NET Core 10.&lt;/p&gt;
&lt;p&gt;What you’ll learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to add and configure the IdentityServer middleware&lt;/li&gt;
&lt;li&gt;How to integrate the sample user interface for login and logout&lt;/li&gt;
&lt;li&gt;How to set up test users and in-memory configuration&lt;/li&gt;
&lt;li&gt;How the request pipeline and middleware order work together&lt;/li&gt;
&lt;li&gt;How to configure Serilog for comprehensive logging&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;my-personal-identityserver-journey&quot;&gt;My Personal IdentityServer Journey&lt;/h2&gt;
&lt;p&gt;My deep dive into IdentityServer started when customers began asking about OpenID Connect and authentication. I’ll be honest: these topics intimidated me at first. **_But I live by a simple motto: the things you’re scared of, you should do more often.&lt;/p&gt;
&lt;p&gt;_**So I decided to crack the code.&lt;/p&gt;
&lt;p&gt;I immersed myself in the material, created &lt;a href=&quot;https://tn-data.se/training/&quot;&gt;training workshops&lt;/a&gt;, and forced myself to really understand how everything worked. To cement my learning, I started answering questions on &lt;a href=&quot;https://stackoverflow.com/users/68490/tore-nestenius&quot;&gt;Stack Overflow&lt;/a&gt; and presenting at conferences and user groups.&lt;/p&gt;
&lt;h2 id=&quot;new-to-openid-connect&quot;&gt;New to OpenID-Connect?&lt;/h2&gt;
&lt;p&gt;Before diving into Duende IdentityServer, I highly recommend familiarizing yourself with OpenID Connect and OAuth first. IdentityServer builds on these protocols, and understanding concepts such as tokens, scopes, claims, and flows will make this tutorial much easier to follow.&lt;/p&gt;
&lt;p&gt;To get started, check out my &lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;OpenID Connect for Developers tutorial&lt;/a&gt;, which provides an in-depth introduction tailored for developers.&lt;/p&gt;
&lt;h2 id=&quot;starting-from-scratch&quot;&gt;Starting from Scratch&lt;/h2&gt;
&lt;p&gt;In this tutorial, we’ll start with the ASP.NET Core Empty project template and build a working Duende IdentityServer setup, step by step.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;ASP.NET Core Empty Project in Visual Studio 2026&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;455&quot; height=&quot;102&quot; src=&quot;https://nestenius.se/_astro/ASP.NET-Core-Empty-Project.CmnjL2Fn_18wlbT.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;By starting from scratch, you’ll understand exactly what each component does, why it’s needed, and how everything fits together.&lt;/p&gt;
&lt;h2 id=&quot;why-build-from-scratch&quot;&gt;Why Build from Scratch?&lt;/h2&gt;
&lt;p&gt;Duende provides a set of excellent &lt;a href=&quot;https://docs.duendesoftware.com/identityserver/overview/packaging/#templates&quot;&gt;templates&lt;/a&gt; that get you up and running quickly, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Duende BFF Host using a Remote API&lt;/li&gt;
&lt;li&gt;Duende BFF using a Local API&lt;/li&gt;
&lt;li&gt;Duende BFF with Blazor autorender&lt;/li&gt;
&lt;li&gt;Duende IdentityServer Empty&lt;/li&gt;
&lt;li&gt;Duende IdentityServer Quickstart UI (UI assets only)&lt;/li&gt;
&lt;li&gt;Duende IdentityServer with ASP.NET Core Identity&lt;/li&gt;
&lt;li&gt;Duende IdentityServer with Entity Framework Stores&lt;/li&gt;
&lt;li&gt;Duende IdentityServer with In-Memory Stores and Test Users&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These templates are great for getting started. However, they come with many defaults and preconfigured code that can feel like magic. When something goes wrong or you need to customize the behavior, that magic becomes a problem.&lt;/p&gt;
&lt;p&gt;I believe you should understand everything you bring into your IdentityServer project. That’s why we’ll build our service from scratch, copying only the files and configuration that we need from the template.&lt;/p&gt;
&lt;p&gt;By the end, you’ll have something similar to the &lt;a href=&quot;https://github.com/DuendeSoftware/products/tree/main/identity-server/templates/src/IdentityServerInMem&quot;&gt;&lt;strong&gt;Duende IdentityServer with In-Memory Stores and Test Users&lt;/strong&gt;&lt;/a&gt; template, with one key difference: you’ll understand exactly how everything is put together.&lt;/p&gt;
&lt;h2 id=&quot;step-1-creating-the-identityservice-project&quot;&gt;Step #1: Creating the IdentityService Project&lt;/h2&gt;
&lt;p&gt;Let’s get started by creating a new &lt;strong&gt;Empty ASP.NET Core&lt;/strong&gt; project in &lt;strong&gt;Visual&lt;/strong&gt; &lt;strong&gt;Studio&lt;/strong&gt; &lt;strong&gt;2026&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;ASP.NET Core Empty Project in Visual Studio 2026&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;455&quot; height=&quot;102&quot; src=&quot;https://nestenius.se/_astro/ASP.NET-Core-Empty-Project.CmnjL2Fn_18wlbT.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Use the following settings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project name: &lt;strong&gt;IdentityService&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Framework: &lt;strong&gt;.NET 10&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Configure for HTTPS: &lt;strong&gt;Yes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Application URL: &lt;strong&gt;Use the .dev.localhost TLD&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We won’t enable Docker container support or Aspire orchestration for this project.&lt;/p&gt;
&lt;h2 id=&quot;step-2-setting-the-port-and-removing-the-http-profile&quot;&gt;STEP #2: Setting the Port and Removing the HTTP Profile&lt;/h2&gt;
&lt;p&gt;By default, ASP.NET Core assigns random ports when you create a new project. I always use &lt;strong&gt;port 6001&lt;/strong&gt; for my IdentityServer instances, so I’ll configure that now for this project.&lt;/p&gt;
&lt;p&gt;I also removed the &lt;strong&gt;HTTP profile&lt;/strong&gt; and HTTP application URL. Since this is a local development identity service, we only need HTTPS.&lt;/p&gt;
&lt;p&gt;Note: If you plan to containerize IdentityServer, you may want to keep HTTP support for internal container-to-container communication. For more details, check out my blog post &lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After these changes, your &lt;strong&gt;launchSettings.json&lt;/strong&gt; file should look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;profiles&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;commandName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Project&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;launchBrowser&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;environmentVariables&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;        &quot;ASPNETCORE_ENVIRONMENT&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Development&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;dotnetRunMessages&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;applicationUrl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;$schema&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://json.schemastore.org/launchsettings.json&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-3-adding-the-nuget-packages&quot;&gt;STEP #3: Adding the NuGet Packages&lt;/h2&gt;
&lt;p&gt;Next, we add the following NuGet packages to the project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Duende.IdentityServer&lt;/strong&gt;&lt;br&gt;
The core IdentityServer library that provides OpenID Connect and OAuth 2.0 server functionality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Duende.IdentityModel&lt;/strong&gt;&lt;br&gt;
A helper library for working with claims-based identity, OAuth 2.0, and OpenID Connect. It provides types, helpers, and constants for common protocol operations.&lt;br&gt;
See the &lt;a href=&quot;https://docs.duendesoftware.com/identitymodel/&quot;&gt;documentation&lt;/a&gt; for details.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serilog.AspNetCore&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://serilog.net/&quot;&gt;Serilog&lt;/a&gt; enables structured logging from the start, making it much easier to diagnose issues during development and transition to production logging later.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-4-configuring-logging&quot;&gt;STEP #4: Configuring Logging&lt;/h2&gt;
&lt;p&gt;Next, we configure Serilog to get full logging coverage of our application.&lt;/p&gt;
&lt;p&gt;First, we create a bootstrap logger:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.Logger &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LoggerConfiguration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.MinimumLevel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Enrich.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromLogContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.WriteTo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Console&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;formatProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: CultureInfo.InvariantCulture)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateBootstrapLogger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This bootstrap configuration ensures Serilog is active from the very beginning, reliably capturing any configuration errors or exceptions during the initial setup phase before the full ASP.NET Core host pipeline is constructed.&lt;/p&gt;
&lt;p&gt;The bootstrap logger alone is not enough. We also need to integrate Serilog into ASP.NET Core properly.&lt;/p&gt;
&lt;p&gt;To do this, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wrap the ASP.NET Core startup in a try/catch/finally block to catch fatal exceptions and properly close and flush the logs on exit, ensuring no log statements are lost.&lt;/li&gt;
&lt;li&gt;Configure the ASP.NET Core logging service to use Serilog.&lt;/li&gt;
&lt;li&gt;Add the Serilog request logger to the request pipeline.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s the complete code that implements all three steps:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;try&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Starting host...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; WebApplication.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(args);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddSerilog&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;lc&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        lc.MinimumLevel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .MinimumLevel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Override&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Microsoft&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, LogEventLevel.Warning)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .MinimumLevel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Override&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Microsoft.AspNetCore&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, LogEventLevel.Warning)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .MinimumLevel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Override&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;System&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, LogEventLevel.Warning)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .MinimumLevel.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Override&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Duende&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, LogEventLevel.Debug)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .ReadFrom.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(services)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .Enrich.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromLogContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .WriteTo.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Console&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; builder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseSerilogRequestLogging&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Hello World!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Exception&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ex&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;when&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (ex &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HostAbortedException&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fatal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ex, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Unhandled exception&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;finally&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Shut down complete&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CloseAndFlush&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more details about configuring Serilog for ASP.NET Core, visit the &lt;strong&gt;&lt;a href=&quot;https://github.com/serilog/serilog-aspnetcore&quot;&gt;Serilog.AspNetCore&lt;/a&gt;&lt;/strong&gt; GitHub page.&lt;/p&gt;
&lt;h3 id=&quot;addserilog-vs-useserilog-whats-the-difference&quot;&gt;AddSerilog vs. UseSerilog: What’s the Difference?&lt;/h3&gt;
&lt;p&gt;In the code above, we used the &lt;strong&gt;AddSerilog&lt;/strong&gt; method. However, you might encounter the &lt;strong&gt;UseSerilog&lt;/strong&gt; method in other samples and tutorials.&lt;/p&gt;
&lt;h3 id=&quot;whats-the-difference-between-addserilog-and-useserilog&quot;&gt;&lt;strong&gt;What’s the difference between AddSerilog and UseSerilog?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;There is no difference. If you look at the source code for &lt;strong&gt;&lt;a href=&quot;https://github.com/serilog/serilog-extensions-hosting/blob/main/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs&quot;&gt;UseSerilog&lt;/a&gt;&lt;/strong&gt;, you’ll see that it internally calls &lt;strong&gt;AddSerilog&lt;/strong&gt;. However, you should use &lt;strong&gt;AddSerilog&lt;/strong&gt;, as it is the official public API intended for configuring Serilog in modern .NET applications.&lt;/p&gt;
&lt;h2 id=&quot;step-5-adding-duende-identityserver&quot;&gt;STEP #5: Adding Duende IdentityServer&lt;/h2&gt;
&lt;p&gt;We added the IdentityServer NuGet package to our project earlier. Now it’s time to add it to our application.&lt;/p&gt;
&lt;p&gt;First, we register IdentityServer with the dependency injection container:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; isBuilder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.RaiseErrorEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.RaiseInformationEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.RaiseFailureEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.RaiseSuccessEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Events in IdentityServer represent significant occurrences within the system, such as successful logins, token issuance, and authentication failures. These events are disabled by default, so we enable them here to capture them in our logs.&lt;/p&gt;
&lt;p&gt;Examples of built-in events include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ClientAuthenticationSuccessEvent&lt;/li&gt;
&lt;li&gt;ClientAuthenticationFailureEvent&lt;/li&gt;
&lt;li&gt;TokenIssuedSuccessEvent&lt;/li&gt;
&lt;li&gt;TokenIssuedFailureEvent&lt;/li&gt;
&lt;li&gt;UserLoginSuccessEvent&lt;/li&gt;
&lt;li&gt;UserLoginFailureEvent&lt;/li&gt;
&lt;li&gt;And more…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can read more about events in the Duende IdentityServer &lt;a href=&quot;https://docs.duendesoftware.com/identityserver/diagnostics/events/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We also need to add IdentityServer to the request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This adds the &lt;strong&gt;IdentityServer middleware&lt;/strong&gt;, which intercepts and responds to requests sent to its endpoints, including the discovery document, authorization endpoint, token endpoint, and others.&lt;/p&gt;
&lt;h2 id=&quot;step-6-naming-the-console-window&quot;&gt;STEP #6: Naming the Console Window&lt;/h2&gt;
&lt;p&gt;As your solution grows and you add separate projects for clients and APIs, you’ll often run multiple applications at once. With several console windows open, it can be hard to tell which is which.&lt;br&gt;
A simple fix is to set the console title at startup:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.Title &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;IdentityService&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add this near the top of &lt;strong&gt;Program.cs&lt;/strong&gt;. It’s a small touch that makes debugging much easier, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Naming the console window using Console.Title&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;528&quot; height=&quot;124&quot; src=&quot;https://nestenius.se/_astro/Naming-the-console-window.BLay-XRr_Z2djP60.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-7-adding-authorization&quot;&gt;STEP #7: Adding Authorization&lt;/h2&gt;
&lt;p&gt;IdentityServer needs the &lt;strong&gt;authorization&lt;/strong&gt; middleware to be present in the request pipeline, so we add it by calling &lt;strong&gt;UseAuthorization&lt;/strong&gt;, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseSerilogRequestLogging&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthorization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, () &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Hello World!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that middleware order matters here. &lt;strong&gt;UseAuthorization&lt;/strong&gt; must come after &lt;strong&gt;UseIdentityServer&lt;/strong&gt;, because the authorization middleware relies on a populated &lt;strong&gt;HttpContext.User&lt;/strong&gt;, which is established by the &lt;strong&gt;authentication&lt;/strong&gt; middleware. &lt;strong&gt;UseIdentityServer&lt;/strong&gt; adds the &lt;strong&gt;authentication&lt;/strong&gt; middleware internally, so you do not need to call &lt;strong&gt;UseAuthentication&lt;/strong&gt; separately.&lt;/p&gt;
&lt;p&gt;We also add these two calls to be &lt;strong&gt;explicit&lt;/strong&gt; about what services we register:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthorization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ASP.NET Core will automatically call them for us if we omit them, but I like to always be explicit in my code, so I add them anyway. Being explicit also makes it easier to configure them later if you need to add additional authentication handlers or authorization policies.&lt;/p&gt;
&lt;p&gt;With this in place, we will have a request pipeline that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;ASP.NET Request Pipeline with IdentityServer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;278&quot; src=&quot;https://nestenius.se/_astro/ASP.NET-Core-Request-Pipeline-1024x278.Casl2Ndl_1JDR8r.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Authentication always occurs first, establishing the user’s identity. Then IdentityServer inspects the request to handle any OAuth 2.0 or OpenID Connect protocol requests. Finally, the authorization middleware checks whether the user has permission to access the requested resource.&lt;/p&gt;
&lt;h3 id=&quot;what-about-useauthentication&quot;&gt;What about UseAuthentication?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;UseIdentityServer&lt;/strong&gt; includes a call to &lt;strong&gt;UseAuthentication&lt;/strong&gt;, so it’s not necessary to add it separately.&lt;/p&gt;
&lt;h3 id=&quot;what-does-useidentityserver-do&quot;&gt;What does UseIdentityServer do?&lt;/h3&gt;
&lt;p&gt;Internally, it adds multiple middleware components to the ASP.NET Core request pipeline, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CORS (Cross-Origin Resource Sharing)&lt;/li&gt;
&lt;li&gt;DynamicSchemeAuthentication&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;MutualTlsEndpoint&lt;/li&gt;
&lt;li&gt;IdentityServer&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;want-to-learn-more-about-authentication&quot;&gt;Want to learn more about authentication?&lt;/h3&gt;
&lt;p&gt;My &lt;a href=&quot;https://tn-data.se/courses/authentication-and-authorization-in-aspnet-core/&quot;&gt;Authentication and Authorization in ASP.NET Core workshop&lt;/a&gt; covers how these middleware components work under the hood.&lt;/p&gt;
&lt;h2 id=&quot;step-8-verifying-identityserver-is-running&quot;&gt;STEP #8: Verifying IdentityServer is Running&lt;/h2&gt;
&lt;p&gt;That’s it! We have now implemented a working IdentityServer. But how can we tell that it’s actually working? If we start the application, we only see the default “Hello World!” message.&lt;/p&gt;
&lt;p&gt;The IdentityServer middleware exposes endpoints for handling OAuth 2.0 and OpenID Connect requests.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Duende IdentityServer Endpoints&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;790&quot; height=&quot;290&quot; src=&quot;https://nestenius.se/_astro/Duende-IdentityServer-Endpoints.CfDLFbd0_2lHEb5.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We can verify that everything is working by checking the discovery document. Navigate to:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://identityservice.dev.localhost:6001/.well-known/openid-configuration&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If everything is set up correctly, you should see the discovery JSON document.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;issuer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;jwks_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/.well-known/openid-configuration/jwks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/authorize&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;token_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;userinfo_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/userinfo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;end_session_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/endsession&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;check_session_iframe&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/checksession&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;revocation_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/revocation&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;introspection_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/introspect&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;device_authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/deviceauthorization&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;backchannel_authentication_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/ciba&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;pushed_authorization_request_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identityservice.dev.localhost:6001/connect/par&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;require_pushed_authorization_requests&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;frontchannel_logout_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;frontchannel_logout_session_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;backchannel_logout_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;backchannel_logout_session_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;scopes_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;openid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;profile&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;scope1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;scope2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;offline_access&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;   ...more...&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-is-the-discovery-document&quot;&gt;What is the Discovery Document?&lt;/h3&gt;
&lt;p&gt;Most OpenID Connect compliant servers expose a discovery document at the &lt;strong&gt;/.well-known/openid-configuration&lt;/strong&gt; endpoint. This standardized JSON document tells clients everything they need to know about the identity provider, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issuer&lt;/li&gt;
&lt;li&gt;Authorization endpoint&lt;/li&gt;
&lt;li&gt;Token endpoint&lt;/li&gt;
&lt;li&gt;UserInfo endpoint&lt;/li&gt;
&lt;li&gt;JWKS (signing keys) endpoint&lt;/li&gt;
&lt;li&gt;Supported scopes&lt;/li&gt;
&lt;li&gt;Supported response types&lt;/li&gt;
&lt;li&gt;Supported signing algorithms&lt;/li&gt;
&lt;li&gt;And more…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of hard-coding URLs, clients can discover them at runtime. You can read more about it in the &lt;a href=&quot;https://openid.net/specs/openid-connect-discovery-1_0-final.html&quot;&gt;OpenID Connect Discovery 1.0 specification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The current setup is not particularly useful yet because we’re missing a few things, including configuration, users, and a user interface. We’ll address those next.&lt;/p&gt;
&lt;h2 id=&quot;step-9-adding-the-user-interface&quot;&gt;STEP #9: Adding the User Interface&lt;/h2&gt;
&lt;p&gt;So far, we have created our sample project, added logging, and added IdentityServer. We have seen that IdentityServer is responding to requests. Great! But are we done?&lt;/p&gt;
&lt;p&gt;No. Right now, we have only added the &lt;strong&gt;OpenID Connect endpoints&lt;/strong&gt;. The key point to understand here is that Duende IdentityServer is just a library that implements OAuth and OpenID Connect. There is no user interface. There is no built-in login or logout page.&lt;/p&gt;
&lt;p&gt;This is actually the real power of Duende IdentityServer. By focusing solely on protocol implementation, you follow the &lt;strong&gt;single-responsibility principle&lt;/strong&gt; and have complete freedom to create your own user interface. You can customize the user experience exactly how you want, whether that means matching your company’s branding, adding multi-factor authentication flows, or integrating with external identity providers.&lt;/p&gt;
&lt;p&gt;As shown below, a complete IdentityServer setup typically consists of two parts: the protocol endpoints provided by the middleware, and a user interface for login, logout, and consent that you provide yourself.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Duende IdentityServer User Interface&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;793&quot; height=&quot;380&quot; src=&quot;https://nestenius.se/_astro/Duende-IdentityServer-User-Interface.C2MEBNFU_Z1et2PN.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So how do we implement this? Do we have to build it ourselves?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Luckily, no. Duende provides a sample user interface for us to use. The &lt;strong&gt;Duende templates&lt;/strong&gt; include this UI, but since we’re building our IdentityServer from scratch, we need to add it manually to our project.&lt;/p&gt;
&lt;p&gt;We’ll do that next.&lt;/p&gt;
&lt;h2 id=&quot;step-10-downloading-the-duende-identityserver-source-code&quot;&gt;STEP #10: Downloading the Duende IdentityServer Source Code&lt;/h2&gt;
&lt;p&gt;The sample user interface we will use is available in the main IdentityServer &lt;a href=&quot;https://github.com/DuendeSoftware/products&quot;&gt;GitHub repository&lt;/a&gt;. We’ll be copying files from the &lt;a href=&quot;https://github.com/DuendeSoftware/products/tree/main/identity-server/templates/src/IdentityServerInMem&quot;&gt;&lt;strong&gt;IdentityServerInMem&lt;/strong&gt;&lt;/a&gt; template.&lt;/p&gt;
&lt;p&gt;To download the source code, go to the &lt;strong&gt;releases page&lt;/strong&gt; and download the source code for the version of the Duende IdentityServer NuGet package that you added earlier. I added &lt;strong&gt;version 7.4.4&lt;/strong&gt;, so I downloaded the source code for this release and unzipped it.&lt;/p&gt;
&lt;p&gt;Then navigate to &lt;strong&gt;identity-servertemplatessrcIdentityServerInMem&lt;/strong&gt; and copy the &lt;strong&gt;Pages&lt;/strong&gt; and &lt;strong&gt;wwwroot&lt;/strong&gt; folders to your Visual Studio project.&lt;/p&gt;
&lt;p&gt;In Solution Explorer, you should now see these two folders:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;wwwroot and pages folder in the Duende IdentityServer project&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;251&quot; height=&quot;232&quot; src=&quot;https://nestenius.se/_astro/wwwroot-and-pages-folder-in-the-Duende-IdentityServer-project.DTAjfuEr_Z12cL6J.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Build your application. The solution should build successfully.&lt;/p&gt;
&lt;h3 id=&quot;&quot;&gt;&lt;/h3&gt;
&lt;p&gt;The name ‘Base64Url’ does not exist in the current context&lt;/p&gt;
&lt;p&gt;If you encounter the error: “The name ‘Base64Url’ does not exist in the current context”, then replace:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bytes&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Base64Url.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Decode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(encoded);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; bytes&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Base64Url.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DecodeFromChars&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(encoded);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this change, your application should build successfully.&lt;/p&gt;
&lt;h3 id=&quot;why-this-happens&quot;&gt;Why this happens&lt;/h3&gt;
&lt;p&gt;This is due to a breaking change in Duende.IdentityModel, where the previously deprecated &lt;strong&gt;Base64Url.Encode&lt;/strong&gt; and &lt;strong&gt;Base64Url.Decode&lt;/strong&gt; APIs were removed. The new supported APIs are &lt;strong&gt;EncodeToString&lt;/strong&gt; and &lt;strong&gt;DecodeFromChars&lt;/strong&gt;. For details, see the related &lt;a href=&quot;https://github.com/DuendeSoftware/foss/pull/249&quot;&gt;pull request&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;step-11-adding-mvc-and-razor-pages-support&quot;&gt;STEP #11: Adding MVC and Razor Pages Support&lt;/h2&gt;
&lt;p&gt;The user interface we just added won’t work yet because we haven’t enabled support for ASP.NET Core Razor Pages. To do this, we need to add:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddRazorPages&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This registers the required services to support Razor Pages in ASP.NET Core.&lt;/p&gt;
&lt;p&gt;However, this alone is not enough. We also need to add the endpoint routing middleware to the request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseRouting&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Add this after UseSerilogRequestLogging.)&lt;/p&gt;
&lt;p&gt;Finally, replace the existing MapGet with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapRazorPages&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RequireAuthorization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run the application now, you should see the &lt;strong&gt;Duende IdentityServer welcome page&lt;/strong&gt;. Awesome!&lt;/p&gt;
&lt;p&gt;However, you’ll notice that the static content (images, CSS, and JavaScript) is not loading. We’ll fix that next.&lt;/p&gt;
&lt;h2 id=&quot;step-12-adding-static-content-support&quot;&gt;STEP #12: Adding Static Content Support&lt;/h2&gt;
&lt;p&gt;Adding support for static content is easy. We just add this middleware to our request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseStaticFiles&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add it before &lt;strong&gt;UseSerilogRequestLogging&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Restart the application, and you should now see the standard Duende IdentityServer welcome page:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Duende IdentityServer 7.4.4 welcome page running at https://identityservice.dev.localhost:6001 in a browser&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;500&quot; src=&quot;https://nestenius.se/_astro/Duende-IdentityServer-Welcome-Screen-1-1024x500.BjYr3ABO_1H2Oof.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-13-troubleshooting-fiddler-with-devlocalhost-endpoints&quot;&gt;STEP #13: Troubleshooting Fiddler with dev.localhost Endpoints&lt;/h2&gt;
&lt;p&gt;At this point, we have a fully working IdentityServer instance with a user interface, static content, and authentication in place. As you continue developing and integrating clients and APIs, one thing quickly becomes essential: the ability to inspect HTTP traffic.&lt;/p&gt;
&lt;p&gt;This is especially important when working with OpenID Connect. Many critical requests, such as token requests, introspection calls, and user info lookups, are back-channel requests that occur entirely server-to-server. These requests never appear in browser developer tools.&lt;/p&gt;
&lt;p&gt;For inspecting this traffic, I always use &lt;strong&gt;Fiddler Classic&lt;/strong&gt;, an invaluable debugging tool that you can &lt;a href=&quot;https://www.telerik.com/download/fiddler&quot;&gt;download&lt;/a&gt; for free.&lt;/p&gt;
&lt;h3 id=&quot;the-err_tunnel_connection_failed-error&quot;&gt;The ERR_TUNNEL_CONNECTION_FAILED error&lt;/h3&gt;
&lt;p&gt;If you runFiddler and try to browse your IdentityServer running on a &lt;strong&gt;.dev.localhost&lt;/strong&gt; address, you may encounter the &lt;strong&gt;ERR_TUNNEL_CONNECTION_FAILED&lt;/strong&gt; browser error:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Browser error showing ERR_TUNNEL_CONNECTION_FAILED for https://identityservice.dev.localhost:6001/&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;564&quot; height=&quot;209&quot; src=&quot;https://nestenius.se/_astro/ERR_TUNNEL_CONNECTION_FAILED-ASP.NET-Core-1.CbZ3Mi32_BAYUS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This can be confusing, especially since Fiddler and ASP.NET Core have worked well together for many years.&lt;/p&gt;
&lt;p&gt;So what changed?&lt;/p&gt;
&lt;h3 id=&quot;what-is-the-devlocalhost-domain&quot;&gt;What is the .dev.localhost domain?&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;.dev.localhost&lt;/strong&gt; domain is a new feature introduced in .NET 10 to simplify local HTTPS development. It allows you to use realistic, TLS-secured hostnames without manually managing certificates. You can read more about it in the&lt;br&gt;
&lt;a href=&quot;https://duendesoftware.com/blog/20250923-why-you-should-be-using-dotnet-10-new-tls-certificate&quot;&gt;Why You Should Be Using .NET 10’s New TLS Certificate Certificate&lt;/a&gt; blog post.&lt;/p&gt;
&lt;h3 id=&quot;the-reason-for-err_tunnel_connection_failed&quot;&gt;The reason for ERR_TUNNEL_CONNECTION_FAILED&lt;/h3&gt;
&lt;p&gt;You might wonder:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;**Fiddler and ASP.NET Core have always worked together, so what changed?&lt;br&gt;
**&lt;/em&gt;&lt;br&gt;
The issue lies in how domain names are resolved. Modern browsers (Chrome, Edge, and Firefox) have a built-in rule: they automatically resolve any domain ending in &lt;strong&gt;localhost&lt;/strong&gt; to &lt;strong&gt;127.0.0.1&lt;/strong&gt; internally. They don’t even bother asking your operating system where the site is.&lt;/p&gt;
&lt;p&gt;Fiddler, however, acts as a system proxy. When your browser sends a request through Fiddler, Fiddler asks the Windows OS to resolve the address. Unlike your browser, Windows has no idea where &lt;strong&gt;identityservice.dev.localhost&lt;/strong&gt; is. You can verify this by trying to ping the address in your terminal:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C:&gt;ping identityservice.dev.localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; Ping request could not find host identityservice.dev.localhost. Please check the name and try again.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-fix-updating-the-hosts-file&quot;&gt;The Fix: Updating the Hosts File&lt;/h3&gt;
&lt;p&gt;To fix this, we need to tell Windows where to find our local development domains. We do this by adding an entry to the &lt;strong&gt;hosts&lt;/strong&gt; file located at &lt;strong&gt;C:WindowsSystem32driversetchosts&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Open this file in a text editor by running it as an Administrator, and add the following line:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;127.0.0.1 identityservice.dev.localhost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;hosts&lt;/strong&gt; file does not support wildcards (like &lt;strong&gt;*.dev.localhost&lt;/strong&gt;), so you will need to add a specific entry for each service you create.&lt;/p&gt;
&lt;h3 id=&quot;verifying-the-connection&quot;&gt;Verifying the Connection&lt;/h3&gt;
&lt;p&gt;Once you’ve saved the file, try the &lt;strong&gt;ping&lt;/strong&gt; command again. It should now resolve correctly to your local loopback address:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ping identityserv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;C:\&gt;ping identityservice.dev.localhost&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Pinging identityservice.dev.localhost [127.0.0.1] with 32 bytes of data:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reply from 127.0.0.1: bytes=32 time&amp;#x3C;1ms TTL=128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reply from 127.0.0.1: bytes=32 time&amp;#x3C;1ms TTL=128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reply from 127.0.0.1: bytes=32 time&amp;#x3C;1ms TTL=128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reply from 127.0.0.1: bytes=32 time&amp;#x3C;1ms TTL=128&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you run Fiddler and refresh your application, everything should work seamlessly. You will be able to see all your HTTPS traffic, decrypted and ready for inspection!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Browser dev tools network tab showing 200 HTTPS responses for Duende IdentityServer assets including Bootstrap, jQuery and si&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;841&quot; height=&quot;226&quot; src=&quot;https://nestenius.se/_astro/Fiddler-Request-from-ASP.NET-Core-1.DfS_lnMl_ZLaJcT.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-14-adding-identityserver-users&quot;&gt;STEP #14: Adding IdentityServer Users&lt;/h2&gt;
&lt;p&gt;If we start our application and try to authenticate, perhaps by clicking the “Click here to see the claims for your current session” link (as shown below):&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Duende IdentityServer 7.4.4 welcome page showing links to discovery document, claims, grants, and server side sessions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;302&quot; src=&quot;https://nestenius.se/_astro/Trigger-Authentication-with-Duende-IdentityServer-1-1024x302.BjJD0Uhy_Z21CGqn.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We get this exception:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;System.InvalidOperationException:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;Please call &apos;AddTestUsers(TestUsers.Users)&apos; on the IIdentityServerBuilder in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Startup or remove the TestUserStore from the AccountController.&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;how-do-we-fix-this&quot;&gt;How do we fix this?&lt;/h3&gt;
&lt;p&gt;Conveniently, a class with sample users is included among the files we added earlier when we added the user interface. Locate the &lt;strong&gt;PagesTestUsers.cs&lt;/strong&gt; file and explore its content.&lt;/p&gt;
&lt;p&gt;To add these users to IdentityServer, we simply call the &lt;strong&gt;AddTestUsers&lt;/strong&gt; method:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; isBuilder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        options.Events.RaiseErrorEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        options.Events.RaiseInformationEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        options.Events.RaiseFailureEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        options.Events.RaiseSuccessEvents &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddTestUsers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(TestUsers.Users);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart the application and try to authenticate again. You should now be presented with a login screen. Enter &lt;strong&gt;bob&lt;/strong&gt; as the username and &lt;strong&gt;bob&lt;/strong&gt; as the password, and the login should succeed.&lt;/p&gt;
&lt;p&gt;This confirms that our test users work and that we can authenticate using these credentials.&lt;/p&gt;
&lt;h2 id=&quot;step-15-adding-identityserver-configuration&quot;&gt;STEP #15: Adding IdentityServer Configuration&lt;/h2&gt;
&lt;p&gt;An IdentityServer with users is a good start. However, it won’t be very useful if we don’t define the &lt;strong&gt;IdentityResources&lt;/strong&gt;, &lt;strong&gt;ApiScopes&lt;/strong&gt;, and &lt;strong&gt;Clients&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A sample configuration file can be found in the in-memory template that we copied files from earlier. Go back to the source code and copy the &lt;strong&gt;Config.cs&lt;/strong&gt; file found in &lt;strong&gt;identity-servertemplatessrcIdentityServerInMem&lt;/strong&gt; and add it to the root of your project.&lt;/p&gt;
&lt;p&gt;Open this file in Visual Studio and review its contents. You’ll find some sample resources and clients defined.&lt;/p&gt;
&lt;p&gt;To add these configuration options to IdentityServer, add the following lines after &lt;strong&gt;AddIdentityServer&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// in-memory, code config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;isBuilder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddInMemoryIdentityResources&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Config.IdentityResources);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;isBuilder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddInMemoryApiScopes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Config.ApiScopes);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;isBuilder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddInMemoryClients&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Config.Clients);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more details about these concepts, see my blog post: &lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;&lt;strong&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;step-16-exploring-the-user-interface&quot;&gt;STEP #16: Exploring the User Interface&lt;/h2&gt;
&lt;p&gt;When we added the user interface earlier, you might have noticed that it contains quite an extensive set of pages. As a newcomer, this can feel a bit overwhelming.&lt;/p&gt;
&lt;p&gt;Let’s clarify the key files and folders in the Pages folder:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Folders:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Account&lt;/strong&gt;&lt;br&gt;
Handles user login, logout, account creation, and access denied errors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ciba&lt;/strong&gt;&lt;br&gt;
Implements Client-Initiated Backchannel Authentication (CIBA), allowing authentication requests to be initiated on a separate device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consent&lt;/strong&gt;&lt;br&gt;
Displays the consent screen where users approve or deny client applications access to their data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device&lt;/strong&gt;&lt;br&gt;
Handles the device authorization flow, used for input-constrained devices like smart TVs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diagnostics&lt;/strong&gt;&lt;br&gt;
Displays the current user’s claims and session information, useful for debugging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ExternalLogin&lt;/strong&gt;&lt;br&gt;
Manages authentication with external identity providers such as Google or Azure AD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grants&lt;/strong&gt;&lt;br&gt;
Allows users to view and revoke permissions they have granted to client applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HomeError&lt;/strong&gt;&lt;br&gt;
Displays an error page when something goes wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redirect&lt;/strong&gt;&lt;br&gt;
Handles post-authentication redirects back to the client application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ServerSideSessions&lt;/strong&gt;&lt;br&gt;
Provides a UI for managing server-side sessions when that feature is enabled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared&lt;/strong&gt;&lt;br&gt;
Contains shared layout files, navigation, and validation components used across all pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key Files:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SecurityHeadersAttribute.cs&lt;/strong&gt;&lt;br&gt;
Adds security headers to responses to help prevent common web vulnerabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telemetry.cs&lt;/strong&gt;&lt;br&gt;
Integrates OpenTelemetry for distributed tracing and metrics.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;step-17-additional-configuration-options&quot;&gt;STEP #17: Additional Configuration Options&lt;/h2&gt;
&lt;p&gt;The included user interface pages are great. They provide a solid starting point for implementing your own custom OpenID Connect identity solution.&lt;/p&gt;
&lt;p&gt;However, I want to point out that there are several configuration files scattered throughout the Pages folder that are easy to overlook:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AccountLoginLoginOptions.cs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AccountLogoutLogoutOptions.cs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CibaConsentOptions.cs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ConsentConsentOptions.cs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeviceDeviceOptions.cs&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s worth being aware of these files when customizing the user experience. In particular, &lt;strong&gt;LoginOptions.cs&lt;/strong&gt; and &lt;strong&gt;LogoutOptions.cs&lt;/strong&gt; are important to review early on, as they control fundamental authentication behaviors like allowing local login, enabling remember me, and handling post-logout redirects.&lt;/p&gt;
&lt;h2 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h2&gt;
&lt;p&gt;Below are questions I did not cover in detail above, but that are important for a production-ready setup.&lt;/p&gt;
&lt;h3 id=&quot;do-i-need-data-protection-for-identityserver-in-production&quot;&gt;Do I need Data Protection for IdentityServer in production?&lt;/h3&gt;
&lt;p&gt;IdentityServer relies on ASP.NET Core Data Protection to protect cookies and protocol state. In production, persist the key ring outside the app instance. Otherwise, restarts, redeployments, or scaling out can invalidate cookies and break sign-in flows. Use a shared key store per environment and protect keys at rest.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection&quot;&gt;How to: Use Data Protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/introducing-the-data-protection-api-key-ring-debugger/&quot;&gt;Introducing the Data Protection API Key Ring Debugger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;usestaticfiles-vs-mapstaticassets-which-one-should-i-use&quot;&gt;UseStaticFiles vs MapStaticAssets: which one should I use?&lt;/h3&gt;
&lt;p&gt;If you target .NET 9 or later and your assets are known at build or publish time, prefer &lt;strong&gt;app.MapStaticAssets()&lt;/strong&gt;. It is optimized for static web assets and provides benefits such as build-time compression and fingerprinting that UseStaticFiles does not offer.&lt;/p&gt;
&lt;p&gt;If you serve files added at runtime (for example, user uploads) or files from other locations or providers, you still need &lt;strong&gt;app.UseStaticFiles()&lt;/strong&gt;. For more details, see &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files&quot;&gt;Static files in ASP.NET Core&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;how-do-i-run-duende-identityserver-in-docker-containers&quot;&gt;How do I run Duende IdentityServer in Docker containers?&lt;/h3&gt;
&lt;p&gt;Running IdentityServer in containers is very achievable, but you need to get a few fundamentals right. The most common pitfalls are networking and URLs, especially the difference between container-to-container communication (internal DNS names) and browser-to-container communication (hostnames and ports), as well as correct issuer and redirect URI configuration.&lt;/p&gt;
&lt;p&gt;I cover this end-to-end in a step-by-step series here: &lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We have now created a working &lt;strong&gt;Duende IdentityServer&lt;/strong&gt; implementation from scratch. Along the way, we configured Serilog for comprehensive logging, added the IdentityServer middleware, integrated the sample user interface, and set up test users and configuration. You should now have a solid foundation for understanding how all of these pieces fit together.&lt;/p&gt;
&lt;p&gt;The next step is to create a client application and an API that relies on this IdentityServer for authentication and authorization. However, that is beyond the scope of this blog post. In the meantime, I encourage you to explore the sample UI code, experiment with the configuration in Config.cs, and review the various options files we discussed.&lt;/p&gt;
&lt;p&gt;If you want to learn more about OpenID Connect, OAuth, or Duende IdentityServer, check out my &lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;OpenID Connect for Developers&lt;/a&gt; tutorial and my workshops:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;OIDC workshops on identity and access management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/authentication-and-authorization-in-aspnet-core/&quot;&gt;Authentication and Authorization in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feel free to reach out if you have any questions or want to discuss how to build a robust authentication solution for your organization.&lt;/p&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Have feedback, spotted an issue, or found a typo? I’d love to hear from you! Your input helps improve the content and assists other developers who might encounter similar challenges. &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;Get in touch!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All code for this tutorial is available on &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/DuendeIdentityServer7&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer - IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>identityserver</category></item><item><title>BFF in ASP.NET Core #7 - Introducing the Duende BFF Library</title><link>https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/</link><guid isPermaLink="true">https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/</guid><description>In the previous blog posts in this series, we built our own Backend-for-Frontend (BFF) implementation in ASP.NET Core from scratch. Now, you might be</description><pubDate>Wed, 20 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the previous blog posts in this series, we built our own Backend-for-Frontend (BFF) implementation in ASP.NET Core from scratch. Now, you might be wondering about how much effort it would take to replace our custom solution with the &lt;a href=&quot;https://docs.duendesoftware.com/bff/&quot;&gt;Duende BFF Security Framework&lt;/a&gt;? In this post, we’ll walk through that migration process and see just how straightforward it can be.&lt;/p&gt;
&lt;p&gt;This is a big topic, so I’ve split it into multiple parts. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;Part 3 - Securing the Cookie Session&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;Part 5 - Automatic Token Renewal&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;Part 6 - Securing the BFF using CORS&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 7 - Introducing the Duende BFF Library&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-duende-bff-library&quot;&gt;Introducing the Duende BFF Library&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://duendesoftware.com/products/bff&quot;&gt;Duende BFF (Backend For Frontend) Security Framework&lt;/a&gt; is a comprehensive ASP.NET Core library that’s designed to secure browser-based applications like SPAs and Blazor apps by implementing the modern Backend-For-Frontend security pattern.&lt;/p&gt;
&lt;p&gt;This library provides a robust set of features that address the common security challenges of modern web applications:&lt;/p&gt;
&lt;p&gt;This library provides features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Token extraction attack protection - keeps access tokens server-side&lt;/li&gt;
&lt;li&gt;Built-in CSRF attack protection through custom header requirements&lt;/li&gt;
&lt;li&gt;Server-side OAuth 2.0 support with complete protocol handling&lt;/li&gt;
&lt;li&gt;Multi-frontend support&lt;/li&gt;
&lt;li&gt;API reverse proxy capabilities for secure external API access&lt;/li&gt;
&lt;li&gt;Server-side session state management&lt;/li&gt;
&lt;li&gt;Back-channel logout support&lt;/li&gt;
&lt;li&gt;Blazor authentication state management&lt;/li&gt;
&lt;li&gt;And more…&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;duende-bff-licensing&quot;&gt;Duende BFF Licensing&lt;/h2&gt;
&lt;p&gt;Before implementing &lt;a href=&quot;https://duendesoftware.com/products/bff&quot;&gt;Duende BFF&lt;/a&gt; in your project, it’s important to understand the licensing model. While the library is free for development, testing, and personal projects, production usage may require a commercial license depending on your business size and requirements. Duende Software also offers &lt;a href=&quot;https://duendesoftware.com/products/communityedition&quot;&gt;community editions&lt;/a&gt; and startup-friendly options.&lt;/p&gt;
&lt;p&gt;For the most current licensing information and to determine what applies to your specific situation, visit the Duende Software website directly.&lt;/p&gt;
&lt;h2 id=&quot;the-duende-bff-source-code&quot;&gt;The Duende BFF Source Code&lt;/h2&gt;
&lt;p&gt;You can find all the code for this tutorial series on &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/Backend-For-Frontend&quot;&gt;GitHub&lt;/a&gt;, with the specific implementation for this post located in the &lt;strong&gt;5-Duende.BFF&lt;/strong&gt; project folder. Having the complete working example will help you to follow along and see the migration in action.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: You’ll need to provide your own OIDC authentication server details to run the sample code successfully.&lt;/p&gt;
&lt;h2 id=&quot;adding-the-duendebff-nuget-package&quot;&gt;Adding the Duende.BFF NuGet Package&lt;/h2&gt;
&lt;p&gt;The first step in is to add the following NuGet packages:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Duende Backend For Frontend (BFF) Security Framework NuGet package&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;481&quot; height=&quot;58&quot; src=&quot;https://nestenius.se/_astro/duende-bff-nuget-package-listing.4GPBLQo4_13uIpP.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;Duende.BFF.Yarp NuGet package&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;481&quot; height=&quot;58&quot; src=&quot;https://nestenius.se/_astro/duende-bff-yarp-nuget-package-verified.BwRW7Ixg_ColhH.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.nuget.org/packages/Duende.BFF.Yarp&quot;&gt;&lt;strong&gt;Duende.BFF.Yarp&lt;/strong&gt;&lt;/a&gt; package is needed in order to support remote APIs.&lt;/p&gt;
&lt;h2 id=&quot;removing-duendeaccesstokenmanagementopenidconnect&quot;&gt;Removing Duende.AccessTokenManagement.OpenIdConnect&lt;/h2&gt;
&lt;p&gt;Our previous implementation used the &lt;a href=&quot;https://www.nuget.org/packages/Duende.AccessTokenManagement.OpenIdConnect&quot;&gt;&lt;strong&gt;Duende.AccessTokenManagement.OpenIdConnect&lt;/strong&gt;&lt;/a&gt; package for automatic token management. However, since Duende.BFF already includes this dependency; we need to remove the standalone package from our project to prevent library version conflicts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Failing to remove this package may cause your application to fail during startup due to conflicting dependencies.&lt;/p&gt;
&lt;h2 id=&quot;configuring-duendebff&quot;&gt;Configuring Duende.BFF&lt;/h2&gt;
&lt;p&gt;Now we need to register the Duende BFF services in our Program class:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Add Duende BFF services builder.Services.AddBff()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddRemoteApis&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;AddBff()&lt;/strong&gt; method registers the core BFF framework services (authentication, session management, local API protection), while &lt;strong&gt;AddRemoteApis()&lt;/strong&gt; adds the additional services needed specifically for proxying requests to external APIs using YARP (Yet Another Reverse Proxy).&lt;/p&gt;
&lt;p&gt;Next, we add the BFF middleware to our request pipeline. Note the specific order of these middleware components. &lt;strong&gt;UseBff()&lt;/strong&gt; must be placed before &lt;strong&gt;UseAuthorization()&lt;/strong&gt; because anti-forgery validation needs to happen before authorization decisions are made:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseBff&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthorization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;app.UseBff()&lt;/strong&gt; middleware automatically handles X-CSRF header validation. The middleware examines endpoint metadata to identify BFF-protected endpoints and validates that requests include the required X-CSRF header.&lt;/p&gt;
&lt;p&gt;Since this protection is now handled by the framework, we can now remove our custom CSRF component:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Remove this code &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CheckForCsrfHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-bff-management-endpoints&quot;&gt;The BFF Management Endpoints&lt;/h2&gt;
&lt;p&gt;In our custom BFF implementation, we created a &lt;strong&gt;BffController&lt;/strong&gt; class that handled login, logout, and session information retrieval. With Duende BFF, we can remove this entire controller since the framework provides these endpoints automatically.&lt;/p&gt;
&lt;p&gt;To enable the BFF management endpoints, add this single line to your request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Map BFF endpoints &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapBffManagementEndpoints&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This automatically exposes the following endpoints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/bff/login&lt;/strong&gt;&lt;br&gt;
Initiates the login process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/silent-login&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/silent-login-callback&lt;/strong&gt;&lt;br&gt;
Support silent login flows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/logout&lt;/strong&gt;&lt;br&gt;
Initiates the logout process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/user&lt;/strong&gt;&lt;br&gt;
Returns information about the currently logged-in user.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/backchannel&lt;/strong&gt;&lt;br&gt;
Handles OpenID Connect back-channel logout notifications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/diagnostics&lt;/strong&gt;&lt;br&gt;
Exposes diagnostics information (enabled only in development environments by default)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;mapping-of-local-apis&quot;&gt;Mapping of Local APIs&lt;/h2&gt;
&lt;p&gt;Our existing local API in the &lt;strong&gt;ApiController&lt;/strong&gt; can remain largely unchanged. We simply need to add the &lt;strong&gt;[BffApi]&lt;/strong&gt; attribute to the controller class:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApiController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Route&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)] &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Authorize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BffApi&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ControllerBase&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;[BffApi]&lt;/strong&gt; attribute automatically applies the security measures recommended for browser-based applications following the BFF pattern. This includes CSRF protection and proper token handling.&lt;/p&gt;
&lt;h2 id=&quot;mapping-of-remote-apis&quot;&gt;Mapping of Remote APIs&lt;/h2&gt;
&lt;p&gt;In our custom implementation, we handled remote API proxying through a method in the ApiController. This approach required manually proxying requests to &lt;strong&gt;/bff/remote&lt;/strong&gt; while replacing the session cookie with the appropriate access token.&lt;/p&gt;
&lt;p&gt;As you experienced firsthand, implementing this correctly involved quite a bit of complex code. Duende BFF simplifies this dramatically by leveraging Microsoft’s &lt;strong&gt;YARP&lt;/strong&gt; (Yet Another Reverse Proxy) library to do this work for us.&lt;/p&gt;
&lt;p&gt;This means we can replace our entire custom action method with this single line of code (in &lt;strong&gt;Program.cs&lt;/strong&gt;):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapRemoteBffApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/api/remote&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://www.secure.nu/tokenapi/gettime&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithAccessToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(RequiredTokenType.User);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;his approach significantly reduces the complexity and code required to implement secure remote API access, while also providing a high-performance proxy solution that would be difficult to implement ourselves.&lt;/p&gt;
&lt;h2 id=&quot;tweaking-the-frontend&quot;&gt;Tweaking the frontend&lt;/h2&gt;
&lt;p&gt;We’ll make some small adjustments to the frontend by adding new buttons to the homepage that demonstrate the various BFF endpoints:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Tweaking the frontend We&amp;#39;ll make some small adjustments to the frontend by adding new buttons to the homepage that demonstrate the various BFF endpoints:&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;922&quot; height=&quot;248&quot; src=&quot;https://nestenius.se/_astro/bff-spa-authentication-api-actions-buttons.ItwF1TdI_1vJCEx.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Each button serves a specific testing purpose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Get User&lt;/strong&gt;&lt;br&gt;
Calls the &lt;strong&gt;/bff/user&lt;/strong&gt; endpoint to retrieve data about the currently logged-in user and session information.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diagnostics&lt;/strong&gt;&lt;br&gt;
Calls the &lt;strong&gt;/bff/diagnostics&lt;/strong&gt; endpoint, which returns current user details and client access token for testing purposes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get Identity&lt;/strong&gt;&lt;br&gt;
Calls a custom &lt;strong&gt;/user/GetIdentity&lt;/strong&gt; endpoint that we’ve implemented in a custom UserController class. This endpoint returns the raw ClaimsPrincipal authentication properties, cookie details, and session cookie information as seen by ASP.NET Core controllers. Since ASP.NET Core sometimes abstracts or transforms authentication data, this endpoint helps verify your assumptions during development.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;signing-out&quot;&gt;Signing Out&lt;/h2&gt;
&lt;p&gt;To implement proper logout functionality, we need to make some minor adjustments to our application. Simply calling the &lt;strong&gt;/bff/logout&lt;/strong&gt; endpoint isn’t sufficient. We must include the internal session ID as a query string parameter for security purposes.&lt;/p&gt;
&lt;p&gt;This &lt;strong&gt;session ID&lt;/strong&gt; requirement acts as CSRF protection for the logout endpoint, preventing attackers from maliciously signing users out of their sessions.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;The logout URL with the session ID looks like this:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;/bff/logout?sid=xxxxxxxxxSessionIDxxxxxxxxxxxx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The easiest way to obtain this complete logout URL is through the &lt;strong&gt;/bff/user&lt;/strong&gt; endpoint, which includes a claim named &lt;strong&gt;bff:logout_url&lt;/strong&gt; that contains the properly formatted logout URL.&lt;/p&gt;
&lt;p&gt;You can find all the implementation details for this logout flow in the &lt;strong&gt;site.js&lt;/strong&gt; JavaScript file.&lt;/p&gt;
&lt;h2 id=&quot;testing-our-implementation&quot;&gt;Testing our implementation&lt;/h2&gt;
&lt;p&gt;With all these changes in place, we can verify that our application works exactly as before. Users can sign in to the BFF application and receive an authentication session cookie, maintaining the same user experience while benefiting from the enhanced security and simplified codebase.&lt;/p&gt;
&lt;h2 id=&quot;the-journey-from-diy-to-production-ready&quot;&gt;The Journey from DIY to Production-Ready&lt;/h2&gt;
&lt;p&gt;Migrating from our custom BFF implementation to &lt;strong&gt;Duende BFF&lt;/strong&gt; proved to be remarkably straightforward. The transition required only a few minor adjustments to achieve the same functionality we had before, but with significant improvements under the hood.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;YARP&lt;/strong&gt; &lt;strong&gt;integration&lt;/strong&gt; particularly stands out, providing a much more robust and modern approach to remote API proxying compared to our prototype implementation. This upgrade gives us enterprise-grade reliability with minimal effort.&lt;/p&gt;
&lt;p&gt;While there are many additional configuration options and advanced features we could explore, those topics will have to wait for future blog posts or webinars. The foundation we’ve built here provides a solid starting point for most applications.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Throughout this blog series, we’ve built a comprehensive BFF implementation in ASP.NET Core and secured it using industry best practices. Our goal has been to provide you with a deep understanding of how a BFF works under the hood, whether you plan to build your own solution or leverage an existing one.&lt;/p&gt;
&lt;p&gt;Existing BFF Solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**&lt;a href=&quot;https://duendesoftware.com/products/bff&quot;&gt;Duende.BFF&lt;/a&gt;&lt;br&gt;
**Enterprise-grade solution from the creators of Duende IdentityServer&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oidcproxydotnet/OidcProxy.Net&quot;&gt;&lt;strong&gt;OidcProxy.Net&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
A developer-friendly, open-source identity-aware reverse proxy built on YARP that requires minimal configuration to implement the BFF pattern for SPAs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve demonstrated that the BFF pattern is one of the most effective ways to harden SPA applications. It allows us to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Leverage the browser’s built-in security features to their fullest potential&lt;/li&gt;
&lt;li&gt;Keep sensitive authentication tokens and session data away from the frontend&lt;/li&gt;
&lt;li&gt;Implement robust CORS protection against cross-origin attacks&lt;/li&gt;
&lt;li&gt;Maintain a secure authentication flow without exposing credentials to JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; Understanding how a BFF works under the hood is essential for using it securely and effectively, regardless of whether you build your own or adopt an existing solution.&lt;/p&gt;
&lt;p&gt;With the foundation we’ve built in this series, you’re now equipped to make informed decisions about BFF implementation and configuration in your own applications.&lt;/p&gt;
&lt;p&gt;I hope you’ve found this blog post series valuable! Please &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;share&lt;/a&gt; your thoughts and experiences as you implement these patterns in your own projects.&lt;/p&gt;
&lt;h2 id=&quot;what-did-we-not-cover&quot;&gt;What Did We Not Cover?&lt;/h2&gt;
&lt;p&gt;What we’ve built so far is a solid starting point for a custom BFF setup. However, there are several important production considerations you may want to explore further:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reducing Authentication Cookie Size&lt;/strong&gt;&lt;br&gt;
To keep your authentication cookies small and secure, consider using a cookie session store. Large cookies can impact performance and hit browser size limits. See this blog post for more details: &lt;a href=&quot;https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging and Monitoring&lt;/strong&gt;&lt;br&gt;
Proper logging is essential for troubleshooting and security auditing, but was not included in this walkthrough. Consider implementing structured logging for authentication events, failed requests, and security-related activities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Protection Configuration&lt;/strong&gt;&lt;br&gt;
ASP.NET Core uses the Data Protection API for critical functions like cookie encryption. In production environments, you’ll want to configure this properly! I’ve covered it in the following posts: &lt;a href=&quot;https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&lt;/a&gt;&lt;br&gt;
and &lt;a href=&quot;https://nestenius.se/net/introducing-the-data-protection-api-key-ring-debugger/&quot;&gt;Introducing the Data Protection API Key Ring Debugger&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forwarded Headers Middleware&lt;/strong&gt;&lt;br&gt;
If your application runs behind a reverse proxy or terminates HTTPS at a load balancer or gateway, you’ll need to configure forwarded headers correctly to maintain security. For more details, see my blog post:&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using YARP as a Reverse Proxy&lt;/strong&gt;&lt;br&gt;
In our examples, we hand-coded the proxy logic between the browser and remote APIs. For a more flexible and high-performance solution, consider using YARP (Yet Another Reverse Proxy). &lt;a href=&quot;https://dotnet.github.io/yarp/&quot;&gt;YARP Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content Security Policy (CSP)&lt;/strong&gt;&lt;br&gt;
With the BFF pattern, you can implement a strict Content Security Policy since the frontend is served from your backend. This provides powerful protection against XSS attacks and reduces the risk of compromised browsers executing malicious scripts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared Storage for Scaling&lt;/strong&gt;&lt;br&gt;
When scaling your BFF across multiple instances, you’ll need shared storage (like Redis or SQL Server) for access tokens and session data to prevent race conditions, especially with Identity Providers that don’t allow refresh token reuse.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization Policies&lt;/strong&gt;&lt;br&gt;
Authorization Policies Beyond authentication (which the BFF manages for us), you’ll need to implement proper authorization using ASP.NET Core’s policy-based authorization system to control what authenticated users can access.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/yarp/yarp-overview?&quot;&gt;YARP&lt;/a&gt; (Yet Another Reverse Proxy)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/bff/fundamentals/session/management/&quot;&gt;BFF Session Management Endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://duendesoftware.com/learn/implementing-secure-bff-with-asp-dot-net-core-and-duende-identityserver&quot;&gt;Implement BFF with ASP.NET Core and IdentityServer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/bff/fundamentals/session/management/logout/&quot;&gt;Duende BFF Logout Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://abstarreveld.medium.com/set-up-a-spa-bff-with-asp-net-core-and-angular-in-3-steps-net-8-18527d73bc43&quot;&gt;Set up a SPA+BFF with ASP.NET Core and Angular in 3 steps (.NET 8)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;p&gt;Ready to take your web security skills to the next level? If you want to deepen your understanding of web application security, OpenID Connect, or authentication in .NET, explore the &lt;a href=&quot;https://tn-data.se/training/&quot;&gt;comprehensive workshops available&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These hands-on, practical workshops are specifically designed for developers working with real-world applications and modern identity solutions. You’ll gain actionable knowledge that you can immediately apply to secure your own projects.&lt;/p&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Have feedback, spotted an issue, or found a typo? I’d love to hear from you! Your input helps improve the content and assists other developers who might encounter similar challenges. &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;Get in touch!&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>identityserver</category><category>openid-connect</category></item><item><title>BFF in ASP.NET Core #6 - Securing our BFF with CORS</title><link>https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/</link><guid isPermaLink="true">https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/</guid><description>In this post, we take the next step in securing our Backend-for-Frontend (BFF) by adding robust Cross-Origin Resource Sharing (CORS) protection. CORS is</description><pubDate>Thu, 14 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this post, we take the next step in securing our Backend-for-Frontend (BFF) by adding robust Cross-Origin Resource Sharing (CORS) protection. CORS is essential for defending against a range of cross-origin attacks, and implementing it correctly is crucial for any application that handles sensitive data.&lt;/p&gt;
&lt;p&gt;We’ll explore the types of attacks that CORS helps prevent, walk through its practical implementation in ASP.NET Core, and explain why proper configuration matters.&lt;/p&gt;
&lt;p&gt;This is a big topic, so I’ve split it into multiple parts. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;Part 3 - Securing the Cookie Session&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;Part 5 - Automatic Token Renewal&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 6 - Securing the BFF using CORS&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;Part 7 - Introducing the Duende BFF Library&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-attack-scenario-when-your-site-has-an-evil-twin&quot;&gt;The Attack Scenario: When Your Site Has an Evil Twin&lt;/h2&gt;
&lt;p&gt;Let’s set the stage:&lt;/p&gt;
&lt;p&gt;Our real site is &lt;strong&gt;localtest.me&lt;/strong&gt;. This is the domain we want to protect. But attackers are always looking for new tricks. Imagine someone copies your site and hosts it on a different domain, like &lt;strong&gt;yoggle.com&lt;/strong&gt;. Or, even worse, they manage to run a version on a subdomain, like &lt;strong&gt;hacked.localtest.me&lt;/strong&gt;, after a &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/security/fundamentals/subdomain-takeover&quot;&gt;subdomain takeover&lt;/a&gt; attack.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Attack Scenario: When Your Site Has an Evil Twin - CORS&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;731&quot; height=&quot;665&quot; src=&quot;https://nestenius.se/_astro/bff-cors-cross-origin-vs-same-site-request-diagram.-ltDGbdK_Z23kgcO.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The attacker’s goal is simple. By tweaking the JavaScript on these fake sites, they try to send requests to your real BFF component at &lt;strong&gt;localtest.me&lt;/strong&gt;. With the &lt;strong&gt;credentials&lt;/strong&gt; flag set in their &lt;strong&gt;fetch&lt;/strong&gt; calls, they attempt to include any cookies your users might have for your site:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://localtest.me:5001/api/local&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    credentials: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;include&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why use localtest.me and yoggle.com?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Both domains resolve to 127.0.0.1, making them perfect for local development and testing. Using realistic domain names helps us reason about cross-origin behavior more accurately compared to using just localhost. Tools like &lt;strong&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt;&lt;/strong&gt; let us easily create trusted certificates for HTTPS, taking our setup even closer to production.&lt;/p&gt;
&lt;h4 id=&quot;why-not-use-localhost&quot;&gt;&lt;strong&gt;Why not use localhost?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Browsers treat localhost as “&lt;strong&gt;potentially trustworthy&lt;/strong&gt;” according to the &lt;a href=&quot;https://www.w3.org/TR/secure-contexts/#is-origin-trustworthy&quot;&gt;&lt;strong&gt;Secure Contexts specification&lt;/strong&gt;&lt;/a&gt;. This means features that normally require HTTPS on regular domains may still work over plain HTTP on localhost. While this is convenient for development, it can hide security issues that will only appear in production. For more accurate security testing, it is better to use domains like &lt;strong&gt;localtest.me&lt;/strong&gt; that behave more like real-world environments.&lt;/p&gt;
&lt;h2 id=&quot;preventing-unauthorized-cross-origin-api-requests&quot;&gt;Preventing Unauthorized Cross-Origin API Requests&lt;/h2&gt;
&lt;p&gt;Our current Backend-for-Frontend (BFF) setup already applies strong protections to the session cookie:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HttpOnly&lt;/li&gt;
&lt;li&gt;SameSite=Strict or Lax&lt;/li&gt;
&lt;li&gt;A prefixed cookie name (__Host-)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;__Host-&lt;/strong&gt; prefix is especially valuable. It enforces that the cookie can only be set from the root path (/), that it must be sent over HTTPS, and it cannot include a Domain attribute. This prevents the cookie from being shared with subdomains, even if one is compromised.&lt;/p&gt;
&lt;p&gt;However, one risk still remains: a user could be tricked into visiting a fake version of your site, perhaps through a phishing email or a malicious link. That fake site could then attempt to send requests to your real BFF using the user’s browser. If the target origin matches and your CORS settings are too permissive, the browser may include the session cookie in these requests.&lt;/p&gt;
&lt;p&gt;To better demonstrate this risk, let’s temporarily set the &lt;strong&gt;SameSite&lt;/strong&gt; value to &lt;strong&gt;None&lt;/strong&gt; in our cookie configuration:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCookie&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Cookie.Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;__Host-AuthCookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Cookie.SameSite &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; SameSiteMode.None;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We set &lt;strong&gt;SameSite&lt;/strong&gt; to &lt;strong&gt;None&lt;/strong&gt; here so that browser SameSite restrictions do not interfere with our demonstration.&lt;/p&gt;
&lt;h2 id=&quot;exploring-the-cross-origin-request-problem&quot;&gt;Exploring the Cross-Origin Request Problem&lt;/h2&gt;
&lt;p&gt;The attacker has deployed two copies of our site: one on a different domain (&lt;strong&gt;yoogle.com&lt;/strong&gt;) and one on a subdomain (&lt;strong&gt;hacked.localtest.me&lt;/strong&gt;). These malicious sites attempt to send requests to our legitimate BFF component on &lt;strong&gt;localtest.me&lt;/strong&gt;, and if cookies are present in the browser, they may be included in the request.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Exploring the Cross-Origin Request Problem&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;724&quot; height=&quot;277&quot; src=&quot;https://nestenius.se/_astro/bff-cors-attack-scenario-evil-twin-domain.B4cq4kjg_11971T.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;At first glance, everything appears secure. By default, the browser blocks the response due to missing CORS headers. In the browser console, we see an error like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;CORS Error - Access to fetch at from origin has been blocked by CORS policy.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;583&quot; height=&quot;59&quot; src=&quot;https://nestenius.se/_astro/cors-policy-blocked-cross-origin-fetch-error-browser-console.DI7dfpgz_Z16sQKd.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;And in the application, we observe a failed request:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Failed to fetch error message.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;481&quot; height=&quot;78&quot; src=&quot;https://nestenius.se/_astro/cors-failed-to-fetch-error-response.mDeZ6DkL_1C7QeD.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This looks reassuring, right? The request failed, so we must be protected!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But here’s the critical issue: we’re not actually safe.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confused about CORS?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web Security Fundamentals&lt;/a&gt; workshop covers the complete spectrum of web vulnerabilities and defenses, from XSS and CSRF to secure authentication patterns and coding practices.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-simple-cors-requests&quot;&gt;The Problem with Simple CORS Requests&lt;/h2&gt;
&lt;p&gt;When JavaScript makes a cross-origin request, the browser applies &lt;strong&gt;CORS&lt;/strong&gt; rules to decide whether the response should be passed back to the page.&lt;/p&gt;
&lt;p&gt;By enabling CORS with strict settings, we can control:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which origins are allowed to call our API&lt;/li&gt;
&lt;li&gt;Which HTTP methods and headers can be used&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This helps prevent other sites from making unauthorized API requests using JavaScript.&lt;/p&gt;
&lt;p&gt;We might think this keeps us safe! After all, the application shows an error, and the request “failed,” right?&lt;/p&gt;
&lt;p&gt;But if you inspect the traffic using a tool like &lt;strong&gt;Fiddler&lt;/strong&gt;, you’ll see that the request was still sent to our BFF:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Sample requests in Fiddler&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;542&quot; height=&quot;83&quot; src=&quot;https://nestenius.se/_astro/fiddler-cors-requests-localtest-secure-nu-200-responses.DiO0OCyy_1HKNRE.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;so-whats-going-on&quot;&gt;&lt;strong&gt;So what’s going on?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;To understand this, we need to stop thinking of ‘the page’ as one thing. The web page and the browser that displays it are actually separate components working together, like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;With Cross-Origin Resource Sharing (CORS), we need to separate the page from the browser.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;789&quot; height=&quot;231&quot; src=&quot;https://nestenius.se/_astro/evil-twin-site-cloned-page-cross-origin-attack-diagram.DT_K2P5I_Z167n4c.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When JavaScript on a page makes a request, it doesn’t directly contact the server. Instead, it asks the browser to make the request on its behalf.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How CORS prevents the result to reach the page.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;221&quot; src=&quot;https://nestenius.se/_astro/cors-browser-blocking-cross-origin-request-to-bff.CSiaFRLN_Z1Dj6vs.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In our case, the backend did not return the expected CORS headers, so the browser blocks the response from being delivered to the page. But the request still reaches the server, and it includes your cookies.&lt;/p&gt;
&lt;p&gt;That means the backend might still process the action, even if the page does not receive the result.&lt;/p&gt;
&lt;p&gt;So yes, the request “failed” in the browser, but not on the network. &lt;strong&gt;That is the real problem&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What is the solution?&lt;/p&gt;
&lt;h2 id=&quot;preflight-cors-requests&quot;&gt;Preflight CORS Requests&lt;/h2&gt;
&lt;p&gt;In the previous example, the browser still sent the real request, even though the page was blocked from seeing the response. This can be a problem if the request changes state on the server.&lt;/p&gt;
&lt;p&gt;To prevent the browser from sending the actual request right away, we can make it ask for permission first. This is done using a &lt;strong&gt;preflight reques&lt;/strong&gt;t. The browser first sends a separate &lt;strong&gt;OPTIONS&lt;/strong&gt; request to check if the real request is allowed.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How preflight requests in Cross-Origin Resource Sharing (CORS) work.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;315&quot; src=&quot;https://nestenius.se/_astro/cors-browser-bff-request-flow-cross-origin-check.BZ6S-EkW_1mAHc5.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In some cases, the browser will do this automatically based on the method or headers in the request. But if we want to force the browser to always perform a &lt;strong&gt;preflight&lt;/strong&gt;, we can add a custom header:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://localtest.me:5001/api/remote&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    credentials: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;include&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &apos;X-CSRF&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;X-CSRF&lt;/strong&gt; header can be any custom name. The important part is that it makes the request a non-simple request (one that requires special CORS handling), which causes the browser to run a preflight check before sending it.&lt;/p&gt;
&lt;p&gt;A preflight request looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;OPTIONS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://localtest.me:5001/api/local &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; localtest.me:5001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Request-Method&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Request-Headers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; x-csrf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Origin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://hacked.localtest.me:7001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Referer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://hacked.localtest.me:7001/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;strong&gt;yoogle.com&lt;/strong&gt; is a trusted domain, then the server should respond to the browser’s preflight request with the following headers to allow the actual request:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 204&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; No Content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Wed, 25 Jun 2025 13:06:44 GMT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Allow-Credentials&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Allow-Headers&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; X-CSRF,Content-Type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Allow-Methods&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; GET&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://yoogle.com:6001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Access-Control-Max-Age&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 600&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This response tells the browser that requests from &lt;strong&gt;&lt;a href=&quot;https://yoogle.com:6001&quot;&gt;https://yoogle.com:6001&lt;/a&gt;&lt;/strong&gt; are allowed, including credentials and the specified headers and methods. The &lt;strong&gt;Access-Control-Max-Age&lt;/strong&gt; header allows the result to be cached for 10 minutes.&lt;/p&gt;
&lt;h4 id=&quot;important&quot;&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Preflight requests only happen for &lt;strong&gt;cross-origin&lt;/strong&gt; requests. For &lt;strong&gt;same-origin&lt;/strong&gt; requests, the browser skips this step and sends the request directly.&lt;/p&gt;
&lt;h2 id=&quot;enabling-cors-in-our-bff&quot;&gt;Enabling CORS in our BFF&lt;/h2&gt;
&lt;p&gt;To allow the backend to respond to the browser’s CORS preflight requests, we need to enable and configure CORS in our application.&lt;/p&gt;
&lt;p&gt;Start by adding this configuration to your &lt;strong&gt;Program.cs&lt;/strong&gt; file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCors&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddDefaultPolicy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;policy&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        policy.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithOrigins&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://localtest.me:5001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AllowAnyMethod&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithHeaders&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-CSRF&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AllowCredentials&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;              // Optionally cache for 10 minutes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;              .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SetPreflightMaxAge&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(TimeSpan.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromMinutes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we add the CORS middleware to the request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseRouting&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseCors&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthorization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Order matters here. The CORS middleware must be placed after &lt;strong&gt;UseRouting()&lt;/strong&gt; but before &lt;strong&gt;UseAuthentication()&lt;/strong&gt; and &lt;strong&gt;UseAuthorization()&lt;/strong&gt; to work correctly.&lt;/p&gt;
&lt;p&gt;With this configuration in place, our BFF now has proper CORS protection. The browser will only allow cross-origin requests from trusted origins, and only when they follow the rules we have defined.&lt;/p&gt;
&lt;h2 id=&quot;improving-the-cors-protection&quot;&gt;Improving the CORS Protection&lt;/h2&gt;
&lt;p&gt;Adding the &lt;strong&gt;X-CSRF&lt;/strong&gt; header in frontend requests forces the browser to send a preflight request. But what if an attacker simply skips that header? In that case, the browser might treat the request as a simple request and send it directly, without a preflight.&lt;/p&gt;
&lt;p&gt;To handle this vulnerability, we need to ensure that the backend enforces the presence of the &lt;strong&gt;X-CSRF&lt;/strong&gt; header. If the header is missing or invalid, then the request gets rejected before any processing occurs.&lt;/p&gt;
&lt;p&gt;We can implement this protection using a custom middleware component:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CsrfHeaderMiddleware&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RequestDelegate&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CsrfHeaderMiddleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RequestDelegate&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        _next &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; next;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; InvokeAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HttpContext&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Path.Value&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToLowerInvariant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Check if request is for protected endpoints&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ShouldCheckCsrfHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(path))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; csrfHeader&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Headers[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-CSRF&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                            .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FirstOrDefault&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (csrfHeader &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                context.Response.StatusCode &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 401&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;                              &quot;Missing or invalid X-CSRF header&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(context);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ShouldCheckCsrfHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(path))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StartsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/bff/session&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;||&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StartsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/api/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make it easier to add to the pipeline, we also define this extension method:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CsrfHeaderMiddlewareExtensions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IApplicationBuilder&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CheckForCsrfHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                              this&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IApplicationBuilder&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; builder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseMiddleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CsrfHeaderMiddleware&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in your application setup, add it after the CORS middleware:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseCors&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CheckForCsrfHeader&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This middleware answers a critical question:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;If this header is missing, is it really coming from one of our legitimate clients?&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It provides the backend with a simple yet effective way to block requests that bypass the expected request flow. This protection also guards against simple HTML forms posting directly to your application, since forms cannot set custom headers.&lt;/p&gt;
&lt;h2 id=&quot;removing-the-cors-credentials-option&quot;&gt;Removing the CORS Credentials Option&lt;/h2&gt;
&lt;p&gt;If your frontend only needs to communicate with your own BFF (same-origin requests), consider removing the &lt;strong&gt;credentials: ‘include’&lt;/strong&gt; option from your fetch calls altogether.&lt;/p&gt;
&lt;h4 id=&quot;why-does-this-matter&quot;&gt;&lt;strong&gt;Why does this matter?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;By default, browsers automatically include cookies in &lt;strong&gt;same-origin requests&lt;/strong&gt;. However, without the credentials option, cookies will never be sent on cross-origin requests, even if the backend is misconfigured to allow them.&lt;/p&gt;
&lt;p&gt;This creates an additional layer of defense-in-depth. If a cross-origin request is made accidentally or maliciously, it will be unauthenticated, and your backend will most likely reject it.&lt;/p&gt;
&lt;p&gt;It’s a simple yet effective way to reduce the risk of cross-origin data leakage and provides protection against configuration mistakes.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;javascript&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;https://localtest.me:5001/api/remote&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	headers: {         &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;	    &apos;X-CSRF&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;1&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;In this post, we secured our BFF with robust CORS protection and custom middleware to defend against cross-origin attacks. In the next and final post of this series, we’ll replace our custom BFF implementation with &lt;a href=&quot;https://docs.duendesoftware.com/bff/&quot;&gt;Duende.BFF&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;to &lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;BFF in ASP.NET Core #7 – Introducing the Duende BFF Library&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;Exploring the Forwarded Headers Middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>openid-connect</category></item><item><title>BFF in ASP.NET Core #5 - Automatic Token Renewal</title><link>https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/</link><guid isPermaLink="true">https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/</guid><description>Nobody wants to sign in every hour. Yet that&apos;s exactly what happens when access tokens expire in applications without proper token management. The good</description><pubDate>Wed, 06 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Nobody wants to sign in every hour. Yet that’s exactly what happens when access tokens expire in applications without proper token management. The good news? We can fix this by implementing automatic token renewal in our BFF!&lt;/p&gt;
&lt;p&gt;This is a big topic, so I’ve split it into multiple parts. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;Part 3 - Securing the Cookie Session&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 5 - Automatic Token Renewal&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;Part 6 - Securing the BFF using CORS&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;Part 7 - Introducing the Duende BFF Library&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Our implementation works perfectly for the lifetime of an access token. The catch? That lifetime varies widely based on your identity provider configuration: some use 10-minute tokens for high security, others allow 24 hours or more. Regardless of the duration, when tokens expire, users lose access to remote APIs even though their BFF session remains active. Let’s fix that.&lt;/p&gt;
&lt;p&gt;How do we get a new access token?&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-refresh-token&quot;&gt;Introducing the Refresh Token&lt;/h2&gt;
&lt;p&gt;When we authenticate with OpenID Connect, we can request a special long-lived credential called a refresh token. This token has one job: obtaining new access tokens when the current one expires.&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;During initial login, we request the &lt;strong&gt;offline_access&lt;/strong&gt; scope.&lt;/li&gt;
&lt;li&gt;The identity provider returns three tokens: ID token, access token, and refresh token.&lt;/li&gt;
&lt;li&gt;When the access token expires, we use the refresh token to request a fresh set of tokens.&lt;/li&gt;
&lt;li&gt;The user can continue accessing the remote APIs without knowing this happened.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The refresh token typically lasts much longer (days or weeks) and can be used multiple times to keep renewing access tokens until the user explicitly logs out, or when the refresh token itself expires.&lt;/p&gt;
&lt;p&gt;The refresh token can either be a one-time token that can only be used once (called a rotating refresh token), or a reusable token that can be used multiple times until it expires. With rotating refresh tokens, each time you use the refresh token to get a new access token, you also receive a new refresh token. This approach provides better security since a compromised refresh token can only be used once.&lt;/p&gt;
&lt;h2 id=&quot;the-token-renewal-implementation-challenge&quot;&gt;The Token Renewal Implementation Challenge&lt;/h2&gt;
&lt;p&gt;By requesting the &lt;strong&gt;offline_access&lt;/strong&gt; scope during authentication, we will receive a &lt;strong&gt;refresh token&lt;/strong&gt;. However, ASP.NET Core has no built-in mechanism to use it when access tokens expire automatically.&lt;/p&gt;
&lt;p&gt;When an access token expires, the remote API responds with:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 401&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Unauthorized&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Microsoft-IIS/10.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;WWW-Authenticate&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Bearer error=&quot;invalid_token&quot;, error_description=&quot;The token expired at &apos;06/24/2025 09:24:46&apos;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Date&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Tue, 24 Jun 2025 09:24:47 GMT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Length&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So how do we implement automatic renewal? Let’s explore that next.&lt;/p&gt;
&lt;h2 id=&quot;using-the-duende-access-token-management-library&quot;&gt;Using the Duende Access Token Management Library&lt;/h2&gt;
&lt;p&gt;Instead of building token renewal from scratch, we’ll use the production-tested &lt;a href=&quot;https://docs.duendesoftware.com/accesstokenmanagement/&quot;&gt;Duende.AccessTokenManagement library&lt;/a&gt;. This open-source solution handles the complex security requirements of token management, including automatic renewal with refresh tokens for us.&lt;/p&gt;
&lt;h2 id=&quot;why-not-build-it-yourself&quot;&gt;Why Not Build It Yourself?&lt;/h2&gt;
&lt;p&gt;Token management looks simple but hides serious pitfalls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Race conditions when multiple requests need renewal simultaneously.&lt;/li&gt;
&lt;li&gt;Token storage conflicts in multi-instance deployments.&lt;/li&gt;
&lt;li&gt;Security vulnerabilities from improper token handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Real-world incidents have occurred where custom implementations accidentally exposed tokens to wrong users or leaked credentials through caching errors. These aren’t theoretical risks, instead they’re expensive mistakes that happen when security-critical code is built in-house without extensive testing.&lt;/p&gt;
&lt;h2 id=&quot;about-the-library&quot;&gt;About the Library&lt;/h2&gt;
&lt;p&gt;The library delivers production-tested stability backed by excellent documentation and support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/accesstokenmanagement/&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/DuendeSoftware/foss/tree/main/access-token-management&quot;&gt;Source Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By using this battle-tested library, you get reliable token management without the risk and maintenance burdens of custom code.&lt;/p&gt;
&lt;h2 id=&quot;implementing-the-access-token-management-library&quot;&gt;Implementing the Access Token Management Library&lt;/h2&gt;
&lt;p&gt;Once the package is installed, integrating the library into your application requires just a few lines of code. Register and configure it in your &lt;strong&gt;Program.cs&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The first step is adding the Duende.AccessTokenManagement.OpenIdConnect NuGet package to your project.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;616&quot; height=&quot;56&quot; src=&quot;https://nestenius.se/_astro/duende-access-token-management-openidconnect-nuget-package.4pBXQxCv_Z2vMfDJ.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once the package is installed, integrating the library into your application requires just a few lines of code. Register and configure it in your Program.cs:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnectAccessTokenManagement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.RefreshBeforeExpiration &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TimeSpan.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromSeconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;15&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;RefreshBeforeExpiration&lt;/strong&gt; setting controls when the library proactively refreshes tokens before they expire. In this example, we’re using 15 seconds to demonstrate the renewal process in action. For production applications, you’ll typically want a longer buffer, anywhere from 1-5 minutes depending on your API response time requirements and token lifetime.&lt;/p&gt;
&lt;h2 id=&quot;updating-the-apicontroller&quot;&gt;Updating the ApiController&lt;/h2&gt;
&lt;p&gt;Now we need to update the ApiController to use the access token management service. Start by injecting the service through the constructor:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ControllerBase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HttpClient&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _httpClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IUserTokenManagementService&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _tokenManagement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HttpClient&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; httpClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                         IUserTokenManagementService&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; tokenManagement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        _httpClient &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; httpClient;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        _tokenManagement &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; tokenManagement;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, replace the existing logic that retrieves the access token with the new implementation using the token management service:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Get access token using Duende Access Token Management&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// (handles automatic refresh)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; tokenResult&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _tokenManagement.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetAccessTokenAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(User);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (tokenResult.IsError)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StatusCode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)HttpStatusCode.Unauthorized, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        error &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Failed to get access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        details &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; $&quot;Token error: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tokenResult&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Error&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        statusCode &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 401&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        tokenManagementUsed &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Create HTTP request with Authorization header&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; url&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://www.secure.nu/tokenapi/gettime&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HttpRequestMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(HttpMethod.Get, url);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; authHeader&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AuthenticationHeaderValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bearer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                               tokenResult.AccessToken);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request.Headers.Authorization &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; authHeader;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-the-access-token-management-library&quot;&gt;Using the Access Token Management library&lt;/h2&gt;
&lt;p&gt;With the setup complete, you can now run the application and make calls to the remote API. In Fiddler, you will see that each request from the browser to the BFF triggers a corresponding call to the remote API, as expected.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Using the Access Token Management library&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;591&quot; height=&quot;258&quot; src=&quot;https://nestenius.se/_astro/fiddler-token-renewal-connect-token-request-identityservice.CqTNYR4t_zRihd.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When the access token is about to expire or has become invalid, the Access Token Management library automatically requests a new token from the authorization server (in this case, &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt;). This process is handled entirely by the library, so you do not need to write any manual logic for handling token expiration or refresh.&lt;/p&gt;
&lt;p&gt;You can observe this behavior in the server logs when a token refresh occurs:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;trce: Duende.UserAccessAccessTokenManagementService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Starting user token acquisition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dbug: Duende.UserAccessAccessTokenManagementService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Token for user Bob Smith will be refreshed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Expiration: 06/24/2025 11:37:41 +00:00,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      ForceRenewal:False&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;trce: Duende.UserTokenEndpointService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Refreshing access token using refresh token:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      hash=blsquoA1xJkBGCxQ2kqM4w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dbug: Duende.UserTokenEndpointService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Sending Refresh token request to:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      https://identityservice.secure.nu/connect/token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;dbug: Duende.UserTokenEndpointService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Access Token of type Bearer refreshed with expiration:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      06/24/2025 11:38:12 +00:00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;trce: Duende.UserAccessAccessTokenManagementService&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Returning refreshed token for user: Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;info: System.Net.Http.HttpClient.Default.LogicalHandler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Start processing HTTP request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      GET https://www.secure.nu/tokenapi/gettime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;info: System.Net.Http.HttpClient.Default.ClientHandler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Sending HTTP request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      GET https://www.secure.nu/tokenapi/gettime&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;info: System.Net.Http.HttpClient.Default.ClientHandler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Received HTTP response headers after 42.5267ms - 200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;info: System.Net.Http.HttpClient.Default.LogicalHandler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      End processing HTTP request after 47.1398ms - 200&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Adding automatic access token renewal to the BFF was straightforward, thanks to the Access Token Management library. You can review the complete implementation for this step in the &lt;strong&gt;3-TokenRenewal project&lt;/strong&gt;. The full source code for the entire series is available on &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/Backend-For-Frontend&quot;&gt;GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This enhancement ensures users stay authenticated without interruption, while the library handles all the token management behind the scenes.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;With OpenID Connect and automatic access token management in place, our BFF setup is nearly complete. However, before it is truly production-ready, there are still important security measures to address.&lt;/p&gt;
&lt;p&gt;The next step is to implement proper &lt;strong&gt;CORS&lt;/strong&gt; (Cross-Origin Resource Sharing) support to help protect your application from cross-origin threats. This will be the focus of the next blog post. Stay tuned!&lt;/p&gt;
&lt;p&gt;To &lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;BFF in ASP.NET Core #6 – Securing our BFF with CORS&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;Exploring the Forwarded Headers Middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>identityserver</category><category>openid-connect</category></item><item><title>BFF in ASP.NET Core #4 - Implementing a BFF from scratch</title><link>https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/</link><guid isPermaLink="true">https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/</guid><description>In this blog post, we&apos;ll implement a minimal yet complete Backend-for-Frontend (BFF) in ASP.NET Core. By starting with a simple foundation and adding</description><pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post, we’ll implement a minimal yet complete Backend-for-Frontend (BFF) in ASP.NET Core. By starting with a simple foundation and adding features incrementally, you’ll learn not just how to build a BFF, but why each component matters for securing modern web applications.&lt;/p&gt;
&lt;p&gt;This is a big topic, so I’ve split it into multiple parts. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;Part 3 - Securing the Cookie Session&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;Part 5 - Automatic Token Renewal&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;Part 6 - Securing the BFF using CORS&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;Part 7 - Introducing the Duende BFF Library&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-application-architecture&quot;&gt;The Application Architecture&lt;/h2&gt;
&lt;p&gt;To demonstrate the BFF pattern in action, we’ll build a minimal application with two main components: a jQuery-based web page for the frontend and an ASP.NET Core backend that implements the BFF logic. This simple setup lets us focus on understanding how the core BFF components work together.&lt;/p&gt;
&lt;p&gt;The overall architecture looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Architecture diagram of a BFF in ASP.NET Core showing browser, BFF endpoints, Identity Provider and downstream API&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1019&quot; height=&quot;843&quot; src=&quot;https://nestenius.se/_astro/bff-aspnet-core-architecture-browser-idp-api-endpoints.exTxpEoL_1oWJ8D.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;bff-api-endpoints&quot;&gt;BFF API Endpoints&lt;/h2&gt;
&lt;p&gt;Our BFF exposes five HTTP endpoints that demonstrate the key responsibilities of the pattern:&lt;/p&gt;
&lt;h3 id=&quot;authentication-endpoints&quot;&gt;Authentication Endpoints&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/bff/login&lt;/strong&gt;&lt;br&gt;
Initiates the authentication flow by redirecting the user to the identity provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/logout&lt;/strong&gt;&lt;br&gt;
Terminates the user’s session both locally and at the identity provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/session&lt;/strong&gt;&lt;br&gt;
Returns the current user’s authentication state, including claims and tokens.&lt;br&gt;
⚠️ Demo endpoint only - production applications should never expose tokens to the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;api-endpoints&quot;&gt;API Endpoints&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/api/local&lt;/strong&gt;&lt;br&gt;
A protected endpoint within the BFF that requires authentication via the session cookie.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/api/remote&lt;/strong&gt;&lt;br&gt;
This proxies requests to an external API, automatically attaching the user’s access token from the server-side session.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;A typical BFF supports two types of APIs:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local APIs that terminate within the BFF (authenticated via the session cookie).&lt;/li&gt;
&lt;li&gt;Remote APIs that are proxied to external services (where the BFF exchanges the session cookie for an access token).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From the frontend’s perspective, both types are secured using the same session cookie.&lt;/p&gt;
&lt;h2 id=&quot;the-web-ui&quot;&gt;The WEB UI&lt;/h2&gt;
&lt;p&gt;Our demo interface provides a simple way to interact with all the BFF endpoints:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;jQuery-based BFF demo UI showing authentication actions and a successful remote API JSON response with token forwarding&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;684&quot; height=&quot;417&quot; src=&quot;https://nestenius.se/_astro/bff-jquery-demo-authentication-api-actions-panel.DORBv6lK_ZGEsnA.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Each button demonstrates a core BFF capability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Login&lt;/strong&gt;&lt;br&gt;
Initiates authentication with the identity provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get Session&lt;/strong&gt;&lt;br&gt;
Displays the current authentication state, including claims and tokens. Note: This is for demonstration purposes only; tokens should never be exposed in production environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logout&lt;/strong&gt;&lt;br&gt;
Ends the session and clears authentication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Call Local API&lt;/strong&gt;&lt;br&gt;
Calls the &lt;strong&gt;/api/local&lt;/strong&gt; endpoint, demonstrating cookie-based authentication for BFF-hosted APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Call Remote API&lt;/strong&gt;&lt;br&gt;
Calls the &lt;strong&gt;/api/remote&lt;/strong&gt; endpoint, demonstrating how the BFF proxies requests with proper token handling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The response area shows the raw API responses, making it easy to understand what data flows through each part of the system.&lt;/p&gt;
&lt;h2 id=&quot;getting-the-bff-source-code&quot;&gt;Getting the BFF Source Code&lt;/h2&gt;
&lt;p&gt;All code for this tutorial is available on &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/Backend-For-Frontend&quot;&gt;GitHub&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;The solution contains multiple projects showing the BFF evolution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1-StartCode&lt;br&gt;
Basic application with simple cookie authentication&lt;/li&gt;
&lt;li&gt;2-OpenIDConnect&lt;br&gt;
Adds real identity provider integration&lt;/li&gt;
&lt;li&gt;(Additional stages covered in subsequent sections)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the application, we have added the following two controller classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BffController&lt;br&gt;
It implements the logic for Login, LogOut, and Get Session.&lt;/li&gt;
&lt;li&gt;ApiController&lt;br&gt;
This class implements the local and remote API endpoints.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;starting-point-project-1-startcode&quot;&gt;Starting Point: Project 1-StartCode&lt;/h2&gt;
&lt;p&gt;When you run the first project, you’ll find a minimal setup with two key ASP.NET Core controllers:&lt;/p&gt;
&lt;h3 id=&quot;bffcontroller&quot;&gt;BffController&lt;/h3&gt;
&lt;p&gt;Handles authentication operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/bff/login&lt;/strong&gt; - Currently uses a fixed test user&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/logout&lt;/strong&gt; - Clears the authentication cookie&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/bff/session&lt;/strong&gt; - Returns current user information&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;apicontroller&quot;&gt;&lt;strong&gt;ApiController&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Implements the API endpoints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;/api/local&lt;/strong&gt; - A simple authenticated endpoint&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;/api/remote&lt;/strong&gt; - Placeholder for external API calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This starting point utilizes basic cookie authentication with a hardcoded user, providing a foundation to build upon before adding OpenID Connect integration.&lt;/p&gt;
&lt;p&gt;You can run &lt;strong&gt;1-StartCode&lt;/strong&gt; immediately to see basic BFF functionality in action. The local API endpoints work, but you’ll notice two limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No real authentication or OpenID-Connect support (just a fixed test user).&lt;/li&gt;
&lt;li&gt;The remote API call fails (it needs an access token we don’t have yet).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;adding-openid-connect-support&quot;&gt;Adding OpenID Connect support&lt;/h2&gt;
&lt;p&gt;Now let’s upgrade our basic BFF to use real authentication. This transformation requires five specific changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Add NuGet packages&lt;/strong&gt;&lt;br&gt;
Install the OpenID Connect authentication middleware.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configure OpenID Connect&lt;/strong&gt;&lt;br&gt;
Set up the authentication handler in Program.cs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update the challenge scheme&lt;/strong&gt;&lt;br&gt;
Change from “Cookies” to “oidc”.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove the fixed user logic&lt;/strong&gt;&lt;br&gt;
Delete the SignInUser method from BffController.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update logout behavior&lt;/strong&gt;&lt;br&gt;
Ensure it signs out from both the BFF and the identity provider.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You’ll need to update the OpenID Connect settings with your own identity provider configuration, including client registration details and endpoints.&lt;/p&gt;
&lt;p&gt;After these changes (available in the &lt;strong&gt;2-OpenIDConnect&lt;/strong&gt; project), your BFF now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Authenticates users through a real identity provider&lt;/li&gt;
&lt;li&gt;Stores tokens securely in server-side session&lt;/li&gt;
&lt;li&gt;Provides cookie-based authentication for the frontend&lt;/li&gt;
&lt;li&gt;Proxies remote API calls with proper access tokens&lt;/li&gt;
&lt;li&gt;Handles complete logout flow (local + identity provider)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a fully functional BFF implementation with all the core security features in place.&lt;/p&gt;
&lt;h3 id=&quot;want-to-dive-deeper-into-openid-connect-and-oauth&quot;&gt;&lt;strong&gt;Want to dive deeper into OpenID Connect and OAuth?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If you’re interested in mastering these technologies, I also teach a hands-on workshop: &lt;a href=&quot;https://tn-data.se/courses/introduction-to-openid-connect-and-oauth/&quot;&gt;Introduction to OpenID Connect and OAuth&lt;/a&gt;. It’s a great way to build a solid understanding and get practical experience with real-world scenarios.&lt;/p&gt;
&lt;h2 id=&quot;supporting-remote-apis&quot;&gt;Supporting Remote APIs&lt;/h2&gt;
&lt;p&gt;One of the most valuable features of the BFF pattern is its secure handling of external API calls. Unlike traditional SPAs, which must manage tokens in JavaScript, the BFF acts as a secure reverse proxy, keeping sensitive credentials out of the browser.&lt;/p&gt;
&lt;p&gt;Here’s the complete flow when calling an external API:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Frontend → BFF:&lt;/strong&gt;&lt;br&gt;
The browser sends a request to &lt;strong&gt;/api/remote&lt;/strong&gt; with the session cookie.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cookie → Token:&lt;/strong&gt;&lt;br&gt;
The BFF validates the cookie and retrieves the stored access token.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BFF → API:&lt;/strong&gt;&lt;br&gt;
The BFF forwards the request to the external API with the access token.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API → Frontend:&lt;/strong&gt;&lt;br&gt;
The response flows back through the BFF to the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing BFF pattern: cookie-authenticated request hitting /api/remote endpoint, forwarded with access token to backen&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;940&quot; height=&quot;257&quot; src=&quot;https://nestenius.se/_astro/bff-cookie-to-access-token-api-proxy-diagram.rXM8CcPF_A1jDD.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;This design achieves three critical goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: Browser cookies never leave your domain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: The frontend only deals with one authentication mechanism.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control&lt;/strong&gt;: All token management happens on server-side.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The remote API only recognizes valid access tokens, whereas the browser only recognizes session cookies. This clean separation of concerns is what makes the BFF pattern so effective for modern web applications.&lt;/p&gt;
&lt;h3 id=&quot;wont-this-add-latency&quot;&gt;Won’t This Add Latency?&lt;/h3&gt;
&lt;p&gt;A common concern is whether adding a proxy layer impacts performance. In practice, the overhead is minimal because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connection reuse:&lt;/strong&gt;&lt;br&gt;
With HTTP/2, the browser maintains a single multiplexed connection to your BFF, which is often faster than opening multiple connections to different APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Negligible overhead:&lt;/strong&gt;&lt;br&gt;
Most API calls already traverse multiple layers (load balancers, API gateways, reverse proxies). One more hop rarely makes a difference. Often, one or more database requests are involved, and these take significantly more time than a single network hop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching opportunities:&lt;/strong&gt;&lt;br&gt;
The BFF can implement caching for frequently accessed data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The security benefits far outweigh any minimal latency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API abstraction:&lt;/strong&gt;&lt;br&gt;
Internal service architecture stays hidden from external clients.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralized security:&lt;/strong&gt;&lt;br&gt;
Rate limiting, authentication, and monitoring all happen in one place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attack surface reduction:&lt;/strong&gt;&lt;br&gt;
Only your BFF needs to be hardened against internet-facing threats.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token isolation:&lt;/strong&gt;&lt;br&gt;
Compromised browser code can never access your API tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next?&lt;/h2&gt;
&lt;p&gt;We now have a working BFF with all the core features. To make it production-ready, we’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;&lt;strong&gt;Automatic token renewal (Part 5)&lt;/strong&gt;&lt;/a&gt; - Keep users signed in when access tokens expire.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;&lt;strong&gt;Enhanced security (Part 6)&lt;/strong&gt;&lt;/a&gt; - CORS configuration and additional protections.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try the code and experiment with different scenarios to see the BFF pattern in action!&lt;br&gt;
To &lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;BFF in ASP.NET Core #5 – Automatic Token Renewal&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Keys Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>BFF in ASP.NET Core #3 - The BFF Pattern Explained</title><link>https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/</link><guid isPermaLink="true">https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/</guid><description>The BFF pattern eliminates many SPA security risks, but it introduces a new critical component: the session cookie. This cookie becomes the key to your</description><pubDate>Wed, 23 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The BFF pattern eliminates many SPA security risks, but it introduces a new critical component: the session cookie. This cookie becomes the key to your user’s authentication. If it’s not properly secured, you’ve simply moved the vulnerability from JavaScript tokens to HTTP cookies.&lt;/p&gt;
&lt;p&gt;This post shows you how to properly secure the session cookie using HTTPS, HttpOnly flags, SameSite protection, and other essential browser security features.&lt;/p&gt;
&lt;p&gt;This is a big topic, so I’ve split it into multiple parts. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 3 - Securing the Cookie Session&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;Part 5 - Automatic Token Renewal&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;Part 6 - Securing the BFF using CORS&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;Part 7 - Introducing the Duende BFF Library&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;securing-the-communication-with-the-bff&quot;&gt;Securing the Communication with the BFF&lt;/h2&gt;
&lt;p&gt;The session cookie is the cornerstone of BFF security. After a user logs in, this cookie becomes their digital identity, included with every request from the browser to the BFF.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing a browser sending a session cookie to an ASP.NET Core BFF component, with a man-in-the-middle threat actor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;729&quot; height=&quot;265&quot; src=&quot;https://nestenius.se/_astro/browser-cookie-bff-component-aspnet-core-mitm-threat.D9Ft4iDS_Z1NBRIj.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s where the BFF pattern really shines: when you handle OIDC directly in the browser, you can’t leverage most of the browser’s built-in security mechanisms.&lt;/p&gt;
&lt;p&gt;With the BFF approach, we can finally use the full arsenal of browser security features. Session cookies unlock HttpOnly protection, SameSite controls, Secure flags, and host prefixes. When properly configured, these create multiple layers of protection that can withstand sophisticated attacks and pass rigorous penetration tests.&lt;/p&gt;
&lt;p&gt;But how do we protect this cookie and the communication channel?&lt;/p&gt;
&lt;h2 id=&quot;using-https&quot;&gt;Using HTTPS&lt;/h2&gt;
&lt;p&gt;HTTPS isn’t optional in modern web applications. It’s the foundation that makes everything else possible. Without HTTPS, your BFF implementation will encounter immediate problems: OpenID Connect flows may fail, browsers will block security-critical cookies, and essential security features will not activate.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing HTTPS communication between a browser and a BFF component in ASP.NET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;305&quot; src=&quot;https://nestenius.se/_astro/browser-to-aspnetcore-bff-component-https-communication.CFn6KQkg_fOFjQ.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;securing-the-authentication-cookie---httponly&quot;&gt;Securing the Authentication Cookie - HttpOnly&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;HttpOnly&lt;/strong&gt; flag is your first line of defense against JavaScript-based attacks. This simple yet powerful attribute instructs the browser to block JavaScript access to the authentication cookie, preventing malicious scripts from stealing the session cookie.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.cookie=CfDJ8IgPXE...; HttpOnly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This protection is critical because it directly addresses cross-site scripting (XSS), one of the most common attack vectors. Even if an attacker manages to inject malicious JavaScript into your application, they cannot access or steal the session cookie. It’s one simple change, with a big impact!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; HttpOnly doesn’t block legitimate functionality. Cookies marked with HttpOnly are still automatically included in same-origin requests made by JavaScript (such as fetch or XMLHttpRequest calls), ensuring your application functions normally while remaining protected.&lt;/p&gt;
&lt;p&gt;Adding this flag helps reduce the risk of stolen session cookies through injected scripts.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing browser cookies communicating via HTTPS with a BFF component in ASP.NET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;374&quot; src=&quot;https://nestenius.se/_astro/browser-cookies-https-aspnetcore-bff-component-diagram.B1WH9_GM_fp5qe.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;securing-the-session-cookie--secure&quot;&gt;Securing the Session Cookie – Secure&lt;/h2&gt;
&lt;p&gt;The Secure attribute adds another essential layer of protection by ensuring your session cookie only travels over encrypted HTTPS connections. This simple flag prevents the cookie from ever being sent over via plain HTTP, even by mistake.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.cookie=CfDJ8IgPXE...; HttpOnly; Secure&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This protection is vital for preventing man-in-the-middle attacks. Without the Secure flag, if any part of your application accidentally makes an HTTP request, the session cookie could be transmitted in plain text, potentially exposing it to network eavesdroppers.&lt;/p&gt;
&lt;p&gt;Browsers prevent setting Secure cookies on HTTP sites, with one exception: localhost during local development. Since production BFF implementations run on public HTTPS endpoints, this development exception doesn’t affect your security posture.&lt;/p&gt;
&lt;h2 id=&quot;securing-the-session-cookie--samesite&quot;&gt;Securing the Session Cookie – SameSite&lt;/h2&gt;
&lt;p&gt;Before SameSite protection existed, browsers had a dangerous default behavior where they would send cookies with every request to your domain, regardless of where that request originated. This meant malicious websites could trigger requests to your application and your users’ cookies would be automatically included.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing how a browser sends cookies to mybank.com from malicious pages, illustrating CSRF attack risk&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;747&quot; src=&quot;https://nestenius.se/_astro/browser-cookie-csrf-attack-diagram-mybank.B9dH7iAN_1mx7iK.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This creates a serious vulnerability. Imagine a user is logged into their banking application in one tab, then clicks a malicious link in an email. That malicious site can send hidden requests to the bank’s website, and because the browser automatically includes the user’s session cookie, the bank sees these requests as legitimate. This is the foundation of Cross-Site Request Forgery (CSRF) attacks.&lt;/p&gt;
&lt;p&gt;The SameSite attribute solves this problem by giving you control over when cookies are included in cross-site requests. Modern browsers increasingly require this attribute, and it should be part of every cookie configuration.&lt;/p&gt;
&lt;p&gt;The attribute accepts three values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strict&lt;/strong&gt;&lt;br&gt;
The cookie is only sent with requests originating from the same site. This provides maximum protection but can impact user experience. Users clicking links from external sites (emails, social media) will appear logged out and need to sign in again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lax (default)&lt;/strong&gt;&lt;br&gt;
The cookie is sent for “safe” top-level navigation (like clicking a link) but blocked for potentially dangerous requests (like form submissions from other sites). This prevents most CSRF attacks while maintaining good usability.&lt;/li&gt;
&lt;li&gt;**None&lt;br&gt;
**The cookie is sent with all cross-site requests. This requires the &lt;strong&gt;Secure&lt;/strong&gt; flag and should only be used if your application specifically needs cross-site cookie access. Most BFF implementations should avoid this setting. This is how cookies behaved before the &lt;strong&gt;SameSite&lt;/strong&gt; attribute was introduced.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For BFF implementations, use &lt;strong&gt;SameSite=Strict&lt;/strong&gt; if your application can handle users appearing logged out when arriving from external links. Otherwise, &lt;strong&gt;SameSite=Lax&lt;/strong&gt; provides excellent protection with better usability.&lt;/p&gt;
&lt;p&gt;Here’s what a secure session cookie might look like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.cookie=CfDJ8IgPXE...; HttpOnly; Secure; SameSite=Strict&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;locking-the-session-cookie-to-the-origin&quot;&gt;Locking the Session Cookie to the Origin&lt;/h2&gt;
&lt;p&gt;By default, session cookies are included in all same-site requests. But what exactly qualifies as “same-site”? The answer reveals a significant security blind spot that many developers overlook.&lt;/p&gt;
&lt;h3 id=&quot;understanding-the-same-site-boundary&quot;&gt;&lt;strong&gt;Understanding the Same-Site Boundary&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In browser security, “same-site” is much broader than most people expect. All of these domains are considered part of the same site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;example**.com**&lt;/li&gt;
&lt;li&gt;login.example**.com**&lt;/li&gt;
&lt;li&gt;api.example**.com**&lt;/li&gt;
&lt;li&gt;test.login.example**.com**&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This broad scope applies to all top-level domains, including country-code domains. For example, these are all considered the same site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;example.&lt;strong&gt;co.uk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;login.example.&lt;strong&gt;co.uk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;api.example.&lt;strong&gt;co.uk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;test.login.example.&lt;strong&gt;co.uk&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://publicsuffix.org/&quot;&gt;Public Suffix List&lt;/a&gt; helps browsers decide if two domains belong to the same site or not.&lt;/p&gt;
&lt;h3 id=&quot;the-subdomain-takeover-risk&quot;&gt;The Subdomain Takeover Risk&lt;/h3&gt;
&lt;p&gt;This broad cookie scope creates a vulnerability: if an attacker compromises any subdomain (such as &lt;strong&gt;test.api.example.com&lt;/strong&gt;), they can potentially access cookies intended for your main application. This is known as a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Security/Subdomain_takeovers&quot;&gt;subdomain takeover attack&lt;/a&gt;. &lt;/p&gt;
&lt;h3 id=&quot;the-solution-__host--prefixed-cookie-names&quot;&gt;The Solution: __Host- Prefixed Cookie Names&lt;/h3&gt;
&lt;p&gt;Fortunately, modern browsers provide a powerful solution: prefixed cookie names. The &lt;strong&gt;__Host-&lt;/strong&gt; prefix enables the strongest origin-binding protection available, transforming your cookie from site-scoped to origin-scoped security.&lt;/p&gt;
&lt;p&gt;Here’s how it works: when a cookie name starts with &lt;strong&gt;__Host-&lt;/strong&gt;, browsers automatically activate special security restrictions. This isn’t just a naming convention; instead, it’s a security feature built into the browser itself.&lt;/p&gt;
&lt;p&gt;Here is an example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; __Host-SessionCookie=XXXXX; HttpOnly; Secure; Path=/; SameSite=Strict&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;requirements-for-__host--cookies&quot;&gt;Requirements for __Host- cookies:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Domain attribute&lt;/strong&gt; - This locks the cookie to the exact hostname that set it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path must be /&lt;/strong&gt; - Ensures the cookie applies to the entire origin&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Must include Secure flag&lt;/strong&gt; - Enforces HTTPS-only transmission&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the &lt;strong&gt;__Host-&lt;/strong&gt; prefix, a cookie set by &lt;strong&gt;&lt;a href=&quot;https://example.com&quot;&gt;https://example.com&lt;/a&gt;&lt;/strong&gt; becomes completely isolated. It will never be sent to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;login.example.com&lt;/li&gt;
&lt;li&gt;api.example.com&lt;/li&gt;
&lt;li&gt;test.login.example.com&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This simple naming convention eliminates the risk of subdomain takeover entirely. Your session cookie is now bound to the exact origin that created it, not just the broader site boundary.&lt;/p&gt;
&lt;h2 id=&quot;additional-security-enhancements&quot;&gt;Additional Security Enhancements&lt;/h2&gt;
&lt;p&gt;Once the core cookie protections are in place, you can further strengthen your BFF security with these measures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CORS&lt;/strong&gt;&lt;br&gt;
Cross-Origin Resource Sharing (CORS) controls which origins can communicate with your BFF. We’ll explore this in detail in Part 6, as it deserves dedicated coverage for BFF implementations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content Security Policy (CSP)&lt;/strong&gt;&lt;br&gt;
CSP controls which resources the browser can load, providing strong protection against XSS attacks. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP&quot;&gt;Learn more about CSP&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Essential Security Headers&lt;/strong&gt;&lt;br&gt;
Modern browsers support several security headers that significantly improve your application’s defense:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strict-Transport-Security&lt;/strong&gt;&lt;br&gt;
This header enforces HTTPS connections and prevents &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security&quot;&gt;protocol downgrade attacks&lt;/a&gt; by instructing browsers to only communicate with your server over secure connections. Once set, browsers will automatically redirect HTTP requests to HTTPS and reject connections with invalid certificates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Content-Type-Options: nosniff&lt;/strong&gt;&lt;br&gt;
Prevents browsers from MIME-sniffing responses away from the declared content type. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Content-Type-Options&quot;&gt;Read more about the X-Content-Type-Options header&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Frame-Options&lt;/strong&gt;&lt;br&gt;
Protects against clickjacking by controlling whether your site can be &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Frame-Options&quot;&gt;embedded in frames&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Referrer-Policy&lt;/strong&gt;&lt;br&gt;
Controls the amount of referrer information included with requests.  Read more about the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy&quot;&gt;Referrer-Policy header&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While configuring these headers is beyond the scope of this blog post series, they are an important part of a strong BFF security setup. In ASP.NET Core, most can be added using middleware in the request pipeline.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Securing the session cookie is critical to the BFF pattern’s success. In this post, we’ve covered the essential layers of protection:&lt;/p&gt;
&lt;h3 id=&quot;communication-security&quot;&gt;Communication Security&lt;/h3&gt;
&lt;p&gt;HTTPS - The foundation that enables all other security features&lt;/p&gt;
&lt;h3 id=&quot;cookie-protection&quot;&gt;Cookie Protection&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HttpOnly&lt;/strong&gt; - Blocks JavaScript access, preventing XSS token theft&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure&lt;/strong&gt; - Ensures cookies only travel over encrypted HTTPS connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SameSite&lt;/strong&gt; - Controls cross-site request behavior (use Strict or Lax)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path=/&lt;/strong&gt; - Applies the cookie to your entire application&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;__Host- prefix&lt;/strong&gt; - Locks the cookie to your exact origin, preventing subdomain attacks&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;want-to-dive-deeper&quot;&gt;Want to Dive Deeper?&lt;/h2&gt;
&lt;p&gt;These cookie protections are part of a broader security strategy. My &lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web Security Fundamentals workshop&lt;/a&gt; covers the complete spectrum of web vulnerabilities and defenses, from XSS and CSRF to secure authentication patterns and coding practices.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;
&lt;p&gt;Now that we understand how to secure the session cookie properly, it’s time to put this knowledge into practice. Part 4 walks through implementing a BFF in ASP.NET Core, showing you exactly how to configure all the security features we’ve covered. &lt;/p&gt;
&lt;p&gt;To &lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;BFF in ASP.NET Core #4 – Implementing a BFF from scratch&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;Deploy a container to Azure App Services using Azure CLI and user-assigned managed identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>openid-connect</category></item><item><title>BFF in ASP.NET Core #2 - The BFF Pattern Explained</title><link>https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/</link><guid isPermaLink="true">https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/</guid><description>How do you secure a Single-Page Application without storing tokens in the browser? The answer lies in the Backend-for-Frontend (BFF) pattern. This</description><pubDate>Thu, 17 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;How do you secure a Single-Page Application without storing tokens in the browser? The answer lies in the Backend-for-Frontend (BFF) pattern. This architectural approach shifts authentication complexity to the backend, keeping your frontend simple and secure. Let’s explore how it works and why it’s become the gold standard for SPA security.&lt;/p&gt;
&lt;p&gt;This is a big topic, so I’ve split it into multiple parts. You can jump to the section you need, but for background and context, it’s best to start with part 1:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/&quot;&gt;Part 1 - Introduction&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/strong&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;Part 3 - Securing the Cookie Session&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;Part 5 - Automatic Token Renewal&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;Part 6 - Securing the BFF using CORS&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;Part 7 - Introducing the Duende BFF Library&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-backend-for-frontend-pattern&quot;&gt;The Backend-for-Frontend Pattern&lt;/h2&gt;
&lt;p&gt;Security experts and major OIDC providers increasingly recommend moving authentication out of the browser entirely. This approach is called the &lt;strong&gt;Backend-for-Frontend (BFF) pattern&lt;/strong&gt;, and it’s becoming the standard for securing modern SPAs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a prescription these days&lt;/strong&gt;, not just an optional pattern that exists out there. The security landscape has evolved to the point where storing tokens in the browser is no longer considered acceptable for production applications, making the BFF pattern essential rather than optional.&lt;/p&gt;
&lt;p&gt;The core idea is straightforward: instead of your React, Angular, or Blazor app handling OpenID Connect flows and managing tokens, a dedicated backend component (the BFF) takes complete responsibility for authentication.&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The browser communicates with the BFF using a simple session cookie.&lt;/li&gt;
&lt;li&gt;The BFF handles all OpenID Connect interactions with your identity provider.&lt;/li&gt;
&lt;li&gt;The BFF stores tokens securely on the server side.&lt;/li&gt;
&lt;li&gt;When your SPA needs data, the BFF calls backend APIs using the stored tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The BFF acts as an authentication proxy: your frontend gets the user experience of being “logged in” through a session cookie, while the BFF handles all the complex OIDC flows and token management behind the scenes.&lt;/p&gt;
&lt;h2 id=&quot;where-does-the-bff-pattern-come-from&quot;&gt;Where does the BFF pattern come from?&lt;/h2&gt;
&lt;p&gt;The Backend-for-Frontend pattern didn’t start as a security solution. It originally emerged from the microservices world to solve a different problem: how to efficiently serve multiple types of client applications.&lt;/p&gt;
&lt;p&gt;The core idea was simple but powerful: instead of forcing mobile apps, web browsers, and desktop applications to all use the same generic API, we can create dedicated backend services tailored to each client’s specific needs.&lt;/p&gt;
&lt;p&gt;This approach solved several problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Optimized data delivery:&lt;/strong&gt;&lt;br&gt;
Each client received exactly the data format and structure it needed.&lt;/li&gt;
&lt;li&gt;S&lt;strong&gt;implified frontends:&lt;/strong&gt;&lt;br&gt;
No more complex data transformation logic in client applications.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clear separation of concerns:&lt;/strong&gt;&lt;br&gt;
Backend teams could optimize for specific client requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing BFF APIs for mobile, browser, and desktop clients routing to shared backend microservices&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;590&quot; src=&quot;https://nestenius.se/_astro/bff-pattern-mobile-browser-desktop-api-backends.fD8r7D0o_Z1xiN0l.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This made the frontend simpler and more efficient, since it received exactly the data and structure it needed. It also helped separate any security concerns between different parts of the system.&lt;/p&gt;
&lt;p&gt;This evolution makes perfect sense: if we can create specialized backends to meet data needs, why not create specialized backends to meet security needs? The BFF is like having your authentication specialist that lets your React, Angular, or Blazor app focus purely on user experience.&lt;/p&gt;
&lt;h2 id=&quot;the-backend-for-frontend-pattern-in-practice&quot;&gt;The Backend-For-Frontend Pattern in Practice&lt;/h2&gt;
&lt;p&gt;Now let’s see how this works in practice. With the BFF pattern, we move all authentication logic out of the frontend. Your SPA no longer needs to handle OIDC flows or manage tokens. Instead, a dedicated backend component manages all security concerns.&lt;/p&gt;
&lt;p&gt;The BFF acts as a &lt;strong&gt;confidential client&lt;/strong&gt; to your identity provider, which means it can securely store client secrets and handle the full OIDC flow. Your browser never sees tokens or handles the complex token exchange protocols.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing ASP.NET Core BFF component handling cookie auth with browser and forwarding access tokens to backend APIs&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;561&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-bff-component-cookie-token-flow-diagram.DR1DGsa6_Z2eP4hF.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This architectural shift unlocks powerful security advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Session Cookie Security:&lt;/strong&gt;&lt;br&gt;
The browser communicates with the BFF using a simple session cookie. This allows us to leverage strong, battle-tested browser security features, like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP-only cookies - JavaScript cannot access the session cookie.&lt;/li&gt;
&lt;li&gt;SameSite protection - Prevents cross-site request forgery attacks.&lt;/li&gt;
&lt;li&gt;Secure flag - Ensures cookies only travel over HTTPS.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reduced Attack Surface:&lt;/strong&gt;&lt;br&gt;
With tokens stored securely on the server, we eliminate major attack vectors, like:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No XSS token theft - Malicious scripts can’t steal what isn’t there.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No token exposure - Browser developer tools won’t reveal sensitive tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Simplified frontend - Less security-critical code to review and maintain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates a robust and yet simpler security perimeter that’s difficult to breach:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing a browser communicating with an ASP.NET Core BFF component over a secured channel, with threat actors depicte&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;760&quot; height=&quot;329&quot; src=&quot;https://nestenius.se/_astro/browser-bff-component-aspnet-core-secure-channel-diagram.BHnUWrFV_Z21fVtI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-duende-bff-framework&quot;&gt;The Duende BFF Framework&lt;/h2&gt;
&lt;p&gt;Before we dive into building our own implementation, it’s worth mentioning that a production-ready solution already exists, and that’s the &lt;a href=&quot;https://duendesoftware.com/products/bff&quot;&gt;Duende.BFF security framework&lt;/a&gt;. This commercial product, developed by the team behind &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt;, is currently the only dedicated BFF framework available for ASP.NET Core that I am aware of.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So why build our own instead of using Duende.BFF?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are three key reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Learning and Understanding:&lt;/strong&gt;&lt;br&gt;
Building a BFF from scratch reveals the underlying security principles and common pitfalls that frameworks often abstract away. When you understand how authentication flows work at a fundamental level, you can make better architectural decisions and troubleshoot issues more effectively.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Practical Knowledge:&lt;/strong&gt;&lt;br&gt;
Working through the implementation details gives you the confidence to customize, extend, or debug BFF behavior in your own applications. This hands-on experience is invaluable when dealing with security-critical code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universal Skills:&lt;/strong&gt;&lt;br&gt;
Understanding the fundamentals gives you confidence and knowledge when working with any BFF implementation, whether it’s an existing solution like Duende.BFF or a custom implementation that you build yourself.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For production applications, definitely consider Duende.BFF if it fits your budget and requirements. For learning and understanding the core concepts, building your own implementation provides deeper insights.&lt;/p&gt;
&lt;p&gt;For a comprehensive introduction to the Duende BFF framework, I recommend watching ‘&lt;a href=&quot;https://www.youtube.com/watch?v=6zMSwlGBmxs&quot;&gt;Token Management: Applying the Duende Backend for Frontend (BFF) Security Framework&lt;/a&gt;’ by Erwin van der Valk.&lt;/p&gt;
&lt;h2 id=&quot;whats-inside-a-bff&quot;&gt;What’s Inside a BFF?&lt;/h2&gt;
&lt;p&gt;Now that we understand why BFF is valuable, let’s look under the hood. A typical BFF implementation consists of two core components that work together to provide secure and seamless authentication:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram of a BFF component showing Session Management with OpenID Connect and Token management above an API Proxy layer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;251&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/bff-component-session-management-api-proxy-diagram.M0eVsXff_Z1P3XOf.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Session Management:&lt;/strong&gt;&lt;br&gt;
This component handles the complete OpenID Connect authentication flow with your identity provider. It manages the login process, issues secure session cookies to the browser, and stores access and refresh tokens safely on the server. Think of it as your authentication orchestrator; it keeps users logged in and handles token lifecycle management entirely behind the scenes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Proxy:&lt;/strong&gt;&lt;br&gt;
This component acts as a secure gateway between your SPA and backend APIs. When your frontend makes API calls, the proxy uses the session cookie to identify the user, retrieves the appropriate access token from storage, and forwards the request to the target API with proper authentication headers attached.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two components work together seamlessly, allowing the session management component to establish trust with the browser, while the API proxy leverages that trust to make authenticated calls on the user’s behalf.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
&lt;p&gt;Now that we understand how BFF works architecturally, it’s time to dive into the security details. In Part 3, we’ll explore how to properly secure the session cookie that travels between your browser and BFF.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To &lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;BFF in ASP.NET Core #3 – The BFF Pattern Explained&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;Exploring the Forwarded Headers Middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/&quot;&gt;AdditionalAuthorizationParameters in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>openid-connect</category></item><item><title>Implementing BFF Pattern in ASP.NET Core for SPAs</title><link>https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/</link><guid isPermaLink="true">https://nestenius.se/net/implementing-bff-pattern-in-asp-net-core-for-spas/</guid><description>This multi-part blog series will show you how to implement secure authentication for Single-Page Applications using the Backend-for-Frontend (BFF) pattern</description><pubDate>Wed, 09 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This multi-part blog series will show you how to implement secure authentication for Single-Page Applications using the &lt;strong&gt;Backend-for-Frontend (BFF)&lt;/strong&gt; pattern with ASP.NET Core.&lt;/p&gt;
&lt;p&gt;We’ll explore why handling OpenID Connect directly in SPAs creates security risks, then build a complete BFF implementation that eliminates browser token storage and follows OAuth 2.0 best practices. In short, it will help you to build secure applications with less complexity in the frontend!&lt;/p&gt;
&lt;p&gt;As this is a big topic, I’m planning to release a new post in this series approximately once a week, with each part building on the previous one. You can jump to the section you need, but for background and context, it’s best to start here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 1 - Introduction&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;Part 2 - Introducing the Backend-for-Frontend (BFF) pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-3-the-bff-pattern-explained/&quot;&gt;Part 3 - Securing the Cookie Session&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-4-implementing-a-bff-from-scratch/&quot;&gt;Part 4 - Implementing a BFF in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-5-automatic-token-renewal/&quot;&gt;Part 5 - Automatic Token Renewal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-6-securing-our-bff-with-cors/&quot;&gt;Part 6 - Securing the BFF using CORS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-7-introducing-the-duende-bff-library/&quot;&gt;Part 7 - Introducing the Duende BFF Library&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-problem-with-direct-oidc-in-spas&quot;&gt;The Problem with Direct OIDC in SPAs&lt;/h2&gt;
&lt;p&gt;In my work, I regularly encounter development teams implementing OpenID Connect authentication directly in their Single-Page Applications. The typical flow looks like this: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser-based frontend authenticates directly with the OIDC provider.&lt;/li&gt;
&lt;li&gt;The OIDC JavaScript library stores the received tokens in the browser.&lt;/li&gt;
&lt;li&gt;Uses them to call backend APIs.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing a browser SPA sending access tokens directly to Payment, Sales, and Order APIs via an Identity Provider&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;989&quot; height=&quot;704&quot; src=&quot;https://nestenius.se/_astro/spa-direct-oidc-browser-access-tokens-multiple-apis.DvIGysfy_Z10yyur.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This approach seems straightforward, but it introduces significant security risks and complexity.&lt;/p&gt;
&lt;p&gt;**so what exactly are these risks?&lt;br&gt;
**&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-tokens-in-the-browser&quot;&gt;The Problem With Tokens In the Browser&lt;/h2&gt;
&lt;p&gt;Let’s explore the key problems with this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protecting the tokens:&lt;/strong&gt;&lt;br&gt;
Storing tokens (access token, ID token, and refresh token) in the browser puts them at risk of theft through cross-site scripting (XSS) attacks or malicious third-party scripts. For example, a compromised browser extension could easily access tokens stored in localStorage, giving attackers full access to your APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Increased attack surface:&lt;/strong&gt;&lt;br&gt;
Adding OpenID Connect support directly to your SPA means handling tokens, redirects, and callbacks in JavaScript. This prevents you from utilizing secure browser features, such as HTTP-only cookies and same-site protections, while significantly increasing your application’s attack surface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Added Complexity:&lt;/strong&gt;&lt;br&gt;
OpenID Connect and token handling introduce a lot of complexity to your frontend. Even with libraries, you need to understand token flows, configure edge cases like renewal and error handling, and manage logout scenarios. This increases security-critical code that requires careful writing, testing, and review.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using a trusted library:&lt;/strong&gt;&lt;br&gt;
Searching npm for “&lt;a href=&quot;https://www.npmjs.com/search?q=oidc&quot;&gt;oidc&lt;/a&gt;” returns thousands of JavaScript libraries, but only a small number are &lt;a href=&quot;https://openid.net/developers/certified-openid-connect-implementations/&quot;&gt;certified&lt;/a&gt; by the OpenID Foundation. Using an unmaintained or insecure library can introduce subtle bugs or serious security vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These problems are well-documented in the security community. Rather than diving deep into every detail, I recommend these excellent talks that explain the issues clearly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=UBFx3MSu1Rc&quot;&gt;Using the BFF pattern to secure SPA and Blazor Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=lEnbi4KClVw&quot;&gt;alert‘OAuth 2 0’; // The impact of XSS on OAuth 2.0 in SPAs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=urS9wstmN2U&quot;&gt;Web Security and BFF with Philippe De Ryck&lt;/a&gt;&lt;br&gt;
Philippe is one of the authors of the OAuth 2.0 for Browser-Based Applications guide.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Curious about OpenID-Connect?&lt;/strong&gt;&lt;br&gt;
Head over to my &lt;a href=&quot;http://tn-data.se/openid-connect/&quot;&gt;OpenID Connect for Developers&lt;/a&gt; article for an accessible, in-depth introduction to OpenID Connect for developers.&lt;/p&gt;
&lt;h2 id=&quot;the-impact-of-stolen-tokens&quot;&gt;The Impact of Stolen Tokens&lt;/h2&gt;
&lt;p&gt;The impact extends far beyond browser access. Once an attacker exfiltrates these tokens, they gain portable, offline access to your APIs. The stolen access token can be used from any client application or system. The attacker doesn’t need continued access to the victim’s browser.&lt;/p&gt;
&lt;p&gt;This means they can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Call your APIs from their own systems.&lt;/li&gt;
&lt;li&gt;Use automated tools to exploit the token.&lt;/li&gt;
&lt;li&gt;Continue accessing resources until the token expires (potentially hours or days later).&lt;/li&gt;
&lt;li&gt;Potentially use the token across multiple applications if it has a broad scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Refresh tokens make this even worse. If a refresh token is also stolen, the attacker can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate new access tokens indefinitely.&lt;/li&gt;
&lt;li&gt;Maintain persistent access even after the original access token expires.&lt;/li&gt;
&lt;li&gt;Continue the attack for weeks or months until the refresh token is revoked or expired.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This transforms a temporary browser compromise into a persistent, distributed security threat that’s much harder to detect and contain.&lt;/p&gt;
&lt;h2 id=&quot;the-solution-the-backend-for-frontend-bff-pattern&quot;&gt;The Solution: the Backend-for-Frontend (BFF) Pattern&lt;/h2&gt;
&lt;p&gt;So what’s the better approach? Instead of handling authentication directly in the SPA, we move all token management to the backend and use a simple session cookie for communication between the frontend and backend.&lt;/p&gt;
&lt;p&gt;This creates a much cleaner and more secure architecture:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing SPA using a cookie to communicate with ASP.NET Core BFF, which exchanges access tokens with Payment, Sales an&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;869&quot; height=&quot;460&quot; src=&quot;https://nestenius.se/_astro/spa-aspnet-core-bff-cookie-token-identity-provider-flow.C-Uy89EE_2qXG8p.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;key-principles-of-the-bff-approach&quot;&gt;Key principles of the BFF approach:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No tokens in the browser:&lt;/strong&gt;&lt;br&gt;
Eliminating tokens from the frontend removes a major security risk and reduces complexity. No more worrying about XSS attacks stealing your tokens or trying to implement secure storage in JavaScript.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the browser’s built-in security features:&lt;/strong&gt;&lt;br&gt;
Without frontend token management, you can fully utilize HTTP-only cookies, same-site protection, and secure flags. All of these are battle-tested browser security mechanisms that work without additional configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the frontend simple and focused:&lt;/strong&gt;&lt;br&gt;
Your SPA can concentrate on what it does best: user interface and experience. Authentication complexity stays in the backend where it belongs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Centralize trust and session handling in the backend:&lt;/strong&gt;&lt;br&gt;
The backend handles the OpenID Connect flow, manages sessions, and securely stores tokens. This makes logging, monitoring, and auditing much easier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducing complexity:&lt;/strong&gt;&lt;br&gt;
Moving the authentication into the backend reduces the number of moving parts in the frontend and cuts down the attack surface. This also means fewer security concerns for frontend developers to worry about.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach is called the &lt;strong&gt;Backend-for-Frontend (BFF)&lt;/strong&gt; &lt;strong&gt;pattern&lt;/strong&gt;. It’s not a new concept, but it’s particularly effective for securing modern SPAs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What you’ll learn:&lt;/strong&gt; By the end of this series, you’ll have a solid starter implementation that eliminates browser token storage, leverages HTTP-only cookies, and centralizes authentication in ASP.NET Core, giving you a strong foundation for your own BFF journey.&lt;/p&gt;
&lt;h2 id=&quot;official-recommendation&quot;&gt;Official Recommendation&lt;/h2&gt;
&lt;p&gt;The BFF pattern isn’t just a theoretical improvement; it’s recognized as the current best practice by the OAuth working group. The IETF draft specification for &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps&quot;&gt;OAuth 2.0 for Browser-Based Apps&lt;/a&gt; explicitly states:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“This architecture is strongly recommended for business applications, sensitive applications, and applications that handle personal data.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This official endorsement reinforces the message that moving authentication to the backend is more than a matter of convenience; it is also about meeting established security standards suited to modern web applications in today’s world.&lt;/p&gt;
&lt;h2 id=&quot;why-i-created-this-series&quot;&gt;Why I Created This Series&lt;/h2&gt;
&lt;p&gt;I wrote this series for two main reasons, both rooted in real-world experience.&lt;/p&gt;
&lt;p&gt;First, I wanted to deepen my own understanding of BFF implementations. Reading about authentication patterns is one thing, but actually building, testing, and troubleshooting them reveals nuances you can’t get from just theory alone.&lt;/p&gt;
&lt;p&gt;Second, I needed a comprehensive, working example for the security workshops I run with development teams. When I’m explaining secure SPA architectures, nothing beats having actual ASP.NET Core code that teams can examine, run, and adapt to their own projects.&lt;/p&gt;
&lt;p&gt;You can learn more about &lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;these security workshops here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These workshops focus on practical security implementation: the kind of hands-on knowledge that helps teams avoid common pitfalls and build more secure applications from the start.&lt;/p&gt;
&lt;h2 id=&quot;ready-for-the-deep-dive&quot;&gt;Ready for the Deep Dive?&lt;/h2&gt;
&lt;p&gt;This deep dive follows that same hands-on philosophy - we’ll build a complete BFF implementation together so you can see exactly how it works under the hood (and hopefully with fewer headaches, too!).&lt;/p&gt;
&lt;p&gt;In Part 2, we’ll explore exactly how the BFF pattern works and why it solves these security challenges.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To &lt;a href=&quot;https://nestenius.se/net/bff-in-asp-net-core-2-the-bff-pattern-explained/&quot;&gt;part 2 - The BFF Pattern Explained&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer - IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>openid-connect</category></item><item><title>How to Use KurrentDB for Event Sourcing in C# on Azure</title><link>https://nestenius.se/net/how-to-use-kurrentdb-for-event-sourcing-in-c-on-azure/</link><guid isPermaLink="true">https://nestenius.se/net/how-to-use-kurrentdb-for-event-sourcing-in-c-on-azure/</guid><description>In this blog post, you will learn how to deploy a test instance of KurrentDB to Azure and access it from a console application in .NET.</description><pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post, you will learn how to deploy a test instance of &lt;a href=&quot;https://www.kurrent.io/&quot;&gt;KurrentDB&lt;/a&gt; to Azure and access it from a console application in .NET.&lt;/p&gt;
&lt;h2 id=&quot;why-did-i-write-this-blog-post&quot;&gt;Why did I write this blog post?&lt;/h2&gt;
&lt;p&gt;While updating my &lt;a href=&quot;https://tn-data.se/courses/ddd-cqrs-and-event-sourcing-in-net/&quot;&gt;DDD, CQRS, and Event Sourcing in .NET&lt;/a&gt; training course to the latest version of .NET and C#, I also decided to switch from EventStoreDB to &lt;a href=&quot;https://www.kurrent.io/&quot;&gt;KurrentDB&lt;/a&gt; for the course’s hands-on exercises.&lt;br&gt;
Previously, we ran EventStoreDB locally, but during our corporate training sessions, we found that installing software onto student machines often gets restricted. To work around this, I chose to host KurrentDB in Azure, with a separate instance running for each student.&lt;/p&gt;
&lt;p&gt;As usual, I thought to share my learning with you! This blog post walks through how I deployed KurrentDB to Azure and connected to it from C#.&lt;/p&gt;
&lt;h2 id=&quot;what-is-kurrentdb&quot;&gt;What is KurrentDB?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;KurrentDB&lt;/strong&gt; is the new name for &lt;strong&gt;EventStoreDB&lt;/strong&gt;, one of the first purpose-built databases for event sourcing. It was created by Greg Young, who also introduced the &lt;a href=&quot;https://cqrs.nu&quot;&gt;Command and Query Responsibility Segregation (CQRS)&lt;/a&gt; pattern.&lt;/p&gt;
&lt;p&gt;KurrentDB stores every change to your data as an event. Instead of just saving the latest state, it records what happened, when, and why. This approach lets you rebuild the full history of your system, create audit logs, replay behavior, or build read models that are tailored for specific needs.&lt;/p&gt;
&lt;p&gt;The KurrentDB database is open-source, written in C#, and available on &lt;a href=&quot;https://github.com/kurrent-io/KurrentDB&quot;&gt;GitHub&lt;/a&gt;. The source code is well written and makes for an interesting codebase to review and explore. I find that reading and exploring real-world projects is a great way to pick up new techniques and ideas.&lt;/p&gt;
&lt;h2 id=&quot;is-kurrentdb-a-new-database&quot;&gt;Is KurrentDB a new database?&lt;/h2&gt;
&lt;p&gt;No, it was originally released in 2012 and is one of the few purpose-built event store databases available. It has had many years to mature and evolve. If you’re curious about how it works internally, check out this talk by Gregory Young: &lt;a href=&quot;https://www.youtube.com/watch?v=YUjO1wM0PZM&quot;&gt;How an EventStore actually works&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-event-sourcing&quot;&gt;What is Event Sourcing?&lt;/h2&gt;
&lt;p&gt;Event sourcing is a way of storing data by recording each change as an event. Instead of saving the latest state, you store the full history of everything that happened. This makes it easy to rebuild states, trace changes, and support things like auditing and custom views.&lt;/p&gt;
&lt;p&gt;For example, in a shopping cart, you can store each action as it happens:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;An event-sourced shopping-cart example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;331&quot; src=&quot;https://nestenius.se/_astro/kurrentdb-shopping-cart-event-stream-versions.BtU1NIAS_3giLR.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To get to the current state, you would load all events and apply them one by one to the shopping cart. A big benefit of event sourcing, is that it makes it much easier to understand how and why something changed within a system, which is great for troubleshooting.&lt;/p&gt;
&lt;h2 id=&quot;deploying-kurrentdb-to-azure&quot;&gt;Deploying KurrentDB to Azure&lt;/h2&gt;
&lt;p&gt;There are several ways to host KurrentDB in Azure, but my requirements were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each student should get their own dedicated instance.&lt;/li&gt;
&lt;li&gt;It must be easy to deploy and clean up using a PowerShell script.&lt;/li&gt;
&lt;li&gt;No need to deal with https (TLS) certificates.&lt;/li&gt;
&lt;li&gt;It should be cost-effective.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given these needs, I decided to use &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/container-instances/?WT.mc_id=linkedin&amp;#x26;sharingId=MVP_427602&quot;&gt;Azure Container Instances&lt;/a&gt;. KurrentDB is available as a container image, and you can find the details in their &lt;a href=&quot;https://docs.kurrent.io/server/v25.0/quick-start/installation.html&quot;&gt;installation guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Azure Container Instances are suitable for testing KurrentDB, but should not be used for production. ACI enforces certain restrictions that limit some features KurrentDB relies on to run properly.&lt;/p&gt;
&lt;h2 id=&quot;kurrentdb-and-the-network&quot;&gt;KurrentDB and the network&lt;/h2&gt;
&lt;p&gt;Before we deploy KurrentDB, we need to understand its network configuration. By default, KurrentDB listens to two network ports:&lt;/p&gt;
&lt;h3 id=&quot;port-1113&quot;&gt;&lt;strong&gt;Port 1113&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;KurrentDB uses &lt;strong&gt;port 1113&lt;/strong&gt; for internal TCP traffic between cluster nodes, such as replication and gossip.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Four KurrentDB nodes communicating over port 1113.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;514&quot; height=&quot;425&quot; src=&quot;https://nestenius.se/_astro/kurrentdb-cluster-nodes-port-1113-replication.DNDxvQdc_Z42KyV.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In our case, since we’re running a single-node setup, we don’t need to worry about port &lt;strong&gt;1113&lt;/strong&gt;. This port is used only for internal node-to-node communication and should never be exposed to the public internet or client applications.&lt;/p&gt;
&lt;h3 id=&quot;port-2113&quot;&gt;Port 2113&lt;/h3&gt;
&lt;p&gt;KurrentDB uses by default port &lt;strong&gt;2113&lt;/strong&gt; for client communication over HTTP or HTTPS.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How a client talks to KurrentDB over port 2113&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;893&quot; height=&quot;221&quot; src=&quot;https://nestenius.se/_astro/client-kurrentdb-port-2113-connection-diagram.D7B8XBNH_Z28yspj.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In this example, we expose port &lt;strong&gt;2113&lt;/strong&gt; to the public internet. To simplify setup and avoid dealing with TLS certificates, we run KurrentDB in insecure mode by setting the &lt;strong&gt;KURRENTDB_INSECURE&lt;/strong&gt; environment variable to true. This is fine for testing and development, but it should never be used in production.&lt;/p&gt;
&lt;h2 id=&quot;deploying-kurrentdb-to-azure-container-instances&quot;&gt;Deploying KurrentDB to Azure Container Instances&lt;/h2&gt;
&lt;p&gt;For my first attempt at deploying KurrentDB as an Azure Container Instance, I used the following PowerShell script:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$containerImage = &quot;docker.kurrent.io/kurrent-latest/kurrentdb:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ResourceGroupName = &quot;rg-kurrenteventstore&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$containerName = &quot;kurrentdb-attempt1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$Location = &quot;swedencentral&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Create resource group if it doesn&apos;t exist&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;az group create --name $ResourceGroupName `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                --location $Location `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Create Azure Container Instance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;az container create `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--resource-group $ResourceGroupName `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--name $containerName `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--image $containerImage `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--ports 2113 `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--environment-variables &quot;KURRENTDB_INSECURE=true&quot; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--cpu 1 `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--memory 1 `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--os-type Linux `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--location $Location `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--restart-policy Never `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--dns-name-label &quot;$containerName-$(Get-Random)&quot; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	--query &quot;ipAddress.fqdn&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running the script, we had a working KurrentDB instance deployed to the public internet, accessible on port 2113.&lt;/p&gt;
&lt;p&gt;When navigating to the URL in your browser, you’ll see KurrentDB’s built-in web interface. This gives you a quick way to check that the instance is up and running, and allows you to explore some basic information and diagnostics like below.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The KurrentDB built-in admin user-interface.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;958&quot; height=&quot;866&quot; src=&quot;https://nestenius.se/_astro/kurrentdb-admin-ui-cluster-status-dashboard.Y5d0EFMH_Z1p0WgI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;deploying-multiple-instances-of-kurrentdb&quot;&gt;Deploying multiple instances of KurrentDB&lt;/h2&gt;
&lt;p&gt;Being able to deploy an instance to Azure is awesome. However, I need a script to deploy multiple instances to Azure.&lt;/p&gt;
&lt;p&gt;The modified script eventually became this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$containerImage = &quot;docker.kurrent.io/kurrent-latest/kurrentdb:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ResourceGroupName = &quot;rg-kurrenteventstore2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$Location = &quot;swedencentral&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$instanceCount = 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Create resource group if it doesn&apos;t exist&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;az group create --name $ResourceGroupName `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                --location $Location `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$urls = @()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for ($i = 1; $i -le $InstanceCount; $i++)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    $containerName = &quot;kurrentdb-$i&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	# Create Azure Container Instance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	$fqdn = az container create `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--resource-group $ResourceGroupName `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--name $containerName `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--image $containerImage `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--ports 2113 `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--environment-variables &quot;KURRENTDB_INSECURE=true&quot; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--cpu 1 `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--memory 1 `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--os-type Linux `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--location $Location `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--restart-policy Never `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--dns-name-label &quot;$containerName-$(Get-Random)&quot; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--query &quot;ipAddress.fqdn&quot; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		--output tsv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;	$urls = $urls + &quot;${fqdn}:2113&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Write-Output &quot;`nAll instance URLs:&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$urls&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;kurrent-navigator&quot;&gt;Kurrent Navigator&lt;/h2&gt;
&lt;p&gt;To manage your KurrentDB instance, you can use &lt;a href=&quot;https://navigator.kurrent.io&quot;&gt;Kurrent Navigator&lt;/a&gt;. This is a standalone desktop app with a clean, modern interface for browsing events, inspecting streams, working with projections, and more. It runs on Windows, macOS, and Linux, and it is useful for both daily administration and troubleshooting.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;KurrentDB Navigator desktop application&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;845&quot; src=&quot;https://nestenius.se/_astro/kurrent-navigator-dashboard-azure-container-instance.C4LPGL0h_ryb8x.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;sending-events-to-kurrentdb-from-c&quot;&gt;Sending events to KurrentDB from C#&lt;/h2&gt;
&lt;p&gt;For .NET developers, KurrentDB offers an official &lt;a href=&quot;https://github.com/kurrent-io/EventStore-Client-Dotnet&quot;&gt;.NET client,&lt;/a&gt; which is also available as a &lt;strong&gt;NuGet&lt;/strong&gt; package: &lt;a href=&quot;https://www.nuget.org/packages/KurrentDB.Client&quot;&gt;KurrentDB.Client&lt;/a&gt;. It supports both .NET Framework 4.8 and modern .NET versions. Details about how to use this package can be found in the &lt;a href=&quot;https://docs.kurrent.io/clients/dotnet/v1.0/getting-started.html&quot;&gt;getting started documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The code below creates four events and sends it to KurrentDB:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;the KurrentDB.Client NuGet package&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;483&quot; height=&quot;75&quot; src=&quot;https://nestenius.se/_astro/kurrentdb-client-nuget-package-prerelease.BSLR_5W6_C6JNm.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; KurrentDB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; KurrentDB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Core&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Serialization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;[ServerAddress]&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; settings&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; KurrentDBClientSettings.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;esdb://&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;?tls=false&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;settings.OperationOptions.ThrowOnAppendFailure &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; using&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; KurrentDBClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(settings);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NewOrderCreated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        CustomerID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SalesPersonID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        SalesPersonName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;John Doe&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OrderCancelled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Reason &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Customer changed their mind&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ProductAddedToOrder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ProductId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ProductName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Laptop&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Quantity &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        PricePerUnit &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 1200.00m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ProductRemovedFromOrder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Id &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ProductId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; streamName&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;order-12345&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AppendEventsAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(client, streamName, events);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AppendEventsAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;KurrentDBClient&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                              string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; stream&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                              List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;object&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;events&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Map the events to KurrentDB message type.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; messages&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Message.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;From&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: e,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                  messageId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Uuid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewUuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AppendToStreamAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;streamName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: stream,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                      messages&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: messages);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Event definitions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; NewOrderCreated&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CustomerID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SalesPersonID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SalesPersonName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OrderCancelled&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Reason&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ProductAddedToOrder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ProductId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ProductName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Quantity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; decimal&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PricePerUnit&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ProductRemovedFromOrder&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Id&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ProductId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After sending the events, we can open Kurrent Navigator to view the events stored in the database:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Viewing the four events in KurrentDB Navigator&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;444&quot; height=&quot;125&quot; src=&quot;https://nestenius.se/_astro/kurrentdb-stream-events-order-list.DgGRDZGa_Z14VxYS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;reading-events-from-kurrentdb&quot;&gt;Reading events from KurrentDB&lt;/h2&gt;
&lt;p&gt;Reading from KurrentDB is straightforward. You just need to create a client and specify the stream you want to read from, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; KurrentDB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; System&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; System&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Reading events from KurrentDB...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; server&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;kurrentdb-2-1927695997.swedencentral.azurecontainer.io&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; settings&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; KurrentDBClientSettings.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;esdb://&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;server&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;?tls=false&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;settings.OperationOptions.ThrowOnAppendFailure &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; using&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; KurrentDBClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(settings);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; streamName&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;order-12345&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; events&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ReadStreamAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Direction.Forwards, streamName, StreamPosition.Start)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ev&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; events)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; json&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Encoding.UTF8.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ev.Event.Data.Span);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ev&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Event&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;EventNumber&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an output, we will get:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Reading events from KurrentDB...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;0 - {&quot;id&quot;:&quot;0d0545fe-5242-4c06-8ad5-573cd4a89bd1&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;customerID&quot;:&quot;264c704d-1754-4e56-9f2a-0f8b4202a045&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;salesPersonID&quot;:&quot;3fa6e8f1-c114-4eea-81e0-eefa9fe2a4f4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;salesPersonName&quot;:&quot;John Doe&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;1 - {&quot;id&quot;:&quot;b3f37164-94c6-426d-bfe2-b8a6063f37ac&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;reason&quot;:&quot;Customer changed their mind&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;2 - {&quot;id&quot;:&quot;4b391bb9-b288-4dc9-9f19-ad65dc577cad&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;productId&quot;:&quot;4bcb44e5-6bc6-42dd-b47c-18496a4d5380&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;productName&quot;:&quot;Laptop&quot;,&quot;quantity&quot;:1,&quot;pricePerUnit&quot;:1200.00}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;3 - {&quot;id&quot;:&quot;6f2fa70f-12ec-46e7-b715-aad3f559be32&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     &quot;productId&quot;:&quot;bb5147e0-5fbb-4339-88ce-1249fb002d0b&quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Writing this blog post was a great way to explore everything new from the team behind KurrentDB. I especially enjoyed trying out the Kurrent Navigator, which was completely new to me. Hosting KurrentDB in Azure using Container Instances turned out to be a smooth experience. Just keep in mind that if you’re coming from EventStoreDB, some names and behaviors have changed, which caused a few bumps along the way.&lt;/p&gt;
&lt;h2 id=&quot;curious-about-ddd-cqrs-and-event-sourcing&quot;&gt;Curious about DDD, CQRS, and Event Sourcing?&lt;/h2&gt;
&lt;p&gt;If you’re interested in learning more about these concepts, I offer several training classes, including &lt;a href=&quot;https://tn-data.se/courses/ddd-cqrs-and-event-sourcing-in-net/&quot;&gt;DDD, CQRS and Event Sourcing in .NET&lt;/a&gt; and &lt;a href=&quot;https://tn-data.se/courses/modern-application-architecture/&quot;&gt;Modern Application Architecture&lt;/a&gt;. You can find the full list of workshops on my &lt;a href=&quot;https://tn-data.se/courses&quot;&gt;training page&lt;/a&gt;. I also offer coaching if you’d like help working through architectural challenges.&lt;/p&gt;
&lt;p&gt;The CQRS and Event Sourcing framework that I use in my training can be found at &lt;a href=&quot;https://cqrs.nu&quot;&gt;https://cqrs.nu&lt;/a&gt;. It’s a project I was part of creating. There, you’ll also find an example of how to do BDD-style testing of Event Sourced systems.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eventsourcingbook.com/&quot;&gt;Understanding Eventsourcing book, by Martin Diliger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cqrs.nu&quot;&gt;Getting started with CQRS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LDW0QWie21s&quot;&gt;Greg Young -  A Decade of DDD, CQRS, Event Sourcing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/&quot;&gt;Running Docker in an Azure Windows Virtual Machine – Not so fast!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/&quot;&gt;BearerToken: The new Authentication handler in ASP.NET Core 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/hardware/how-i-built-my-own-sega-mega-drive-hardware-dev-kit-from-scratch/&quot;&gt;How I built my own Sega Mega Drive hardware dev kit from scratch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Container&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>azure</category></item><item><title>Configuring ASP.NET Core Forwarded Headers Middleware</title><link>https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/</link><guid isPermaLink="true">https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/</guid><description>In my previous blog post, I explained what the Forwarded Headers Middleware does and why it matters. In this post, I will show you how to add it to your</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In my &lt;a href=&quot;//net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;previous blog post&lt;/a&gt;, I explained what the Forwarded Headers Middleware does and why it matters. In this post, I will show you how to add it to your application, configure it, and point out a few common issues that can arise in configuring the forwarded headers middleware.&lt;/p&gt;
&lt;p&gt;In this blog post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#testing&quot;&gt;Testing the Forwarded Headers Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#adding&quot;&gt;Adding the ForwardHeaders Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#configuring&quot;&gt;Configuring ForwardHeadersMiddleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#action&quot;&gt;ForwardHeaders Middleware in Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#observing&quot;&gt;Observing the Problem in the Logs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#allowed&quot;&gt;Allowed Forwarded Headers Hosts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#enabledvariable&quot;&gt;The ASPNETCORE_FORWARDEDHEADERS_ENABLED variable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#clouddebugger&quot;&gt;Forwarded Headers in the Cloud Debugger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sourcecode&quot;&gt;Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-sample-forwardheaders-application&quot;&gt;The Sample ForwardHeaders Application&lt;/h2&gt;
&lt;p&gt;Let’s start with a new ASP.NET Core project using the empty template:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;ASP.NET Core Empty Project Template&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;669&quot; height=&quot;77&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-empty-project-template-selection.D7uU_h_U_5RgCe.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then I updated &lt;strong&gt;launchSettings.json&lt;/strong&gt; to listen on ports &lt;strong&gt;5000&lt;/strong&gt; and &lt;strong&gt;5001&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;applicationUrl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://localhost:5001;http://localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, I replaced the default &lt;strong&gt;app.MapGet()&lt;/strong&gt; with this endpoint:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{**route}&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (HttpContext context,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                              IOptions&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ForwardedHeadersOptions&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; forwardedOptions) =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    var requestDetails &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Protocol &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Protocol,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Scheme &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Scheme,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Path.Value,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        PathBase &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.PathBase.Value,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Host &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Host.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        DisplayUrl &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetDisplayUrl&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RemoteIpAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Connection.RemoteIpAddress&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Headers &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Request.Headers&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                  .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToDictionary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; h.Key, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;h&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; h.Value.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        ForwardedHeaders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ForwardedHeaders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GetEnabledFlags&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(forwardedOptions.Value.ForwardedHeaders),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            AllowedHosts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; forwardedOptions.Value.AllowedHosts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            KnownNetworks &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; forwardedOptions.Value.KnownNetworks&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                            .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n.Prefix &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; n.PrefixLength)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                            .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            KnownProxies &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; forwardedOptions.Value.KnownProxies&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                            .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; p.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.ForwardLimit,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.OriginalForHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.OriginalHostHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.OriginalProtoHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.OriginalPrefixHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.ForwardedForHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.ForwardedHostHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.ForwardedProtoHeaderName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            forwardedOptions.Value.ForwardedPrefixHeaderName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    context.Response.ContentType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JsonSerializer.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Serialize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(requestDetails));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A small helper method is also added:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;static&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetEnabledFlags&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForwardedHeaders&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; headers&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Enum.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetValues&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForwardedHeaders&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    	.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;flag&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; flag &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ForwardedHeaders.None &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; headers.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HasFlag&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(flag))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;flag&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; flag.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())         &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When called, the endpoint responds to any request and returns the request details as seen by ASP.NET Core. It’s a simple and effective way to inspect how the middleware works. The response also includes the current configuration for the Forwarded Headers Middleware.&lt;/p&gt;
&lt;h2 id=&quot;testing-the-forwarded-headers-application&quot;&gt;Testing the Forwarded Headers Application&lt;/h2&gt;
&lt;p&gt;To test it, we can add a Visual Studio &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-9.0&amp;#x26;source=docs&quot;&gt;.http file&lt;/a&gt; like this one:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; http://localhost:5000 HTTP/1.1 &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;X-Forwarded-For&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; 123.0.123.1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;X-Forwarded-Proto&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;X-Forwarded-Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; nestenius.se&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lets us send requests with custom headers. To send a request to the application, you just need to start it and click the &lt;strong&gt;Send request&lt;/strong&gt; link:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Sample HTTP request with the X-Forwarded-* headers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;400&quot; height=&quot;165&quot; src=&quot;https://nestenius.se/_astro/http-request-with-x-forwarded-headers-in-jetbrains-rider.DkWaAH52_ZpctfF.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Since we haven’t added the Forwarded Headers middleware yet, the response will look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Protocol&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;HTTP/1.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Scheme&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Path&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;PathBase&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;DisplayUrl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://localhost:5000/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;RemoteIpAddress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;::1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Headers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Forwarded-For&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;123.0.123.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Forwarded-Proto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Forwarded-Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nestenius.se&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;AllowedHosts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;KnownNetworks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;127.0.0.1/8&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;KnownProxies&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;::1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ForwardLimit&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;OriginalForHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Original-For&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;OriginalHostHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Original-Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;OriginalProtoHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Original-Proto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;OriginalPrefixHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Original-Prefix&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ForwardedForHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Forwarded-For&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ForwardedHostHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Forwarded-Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ForwardedProtoHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Forwarded-Proto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;ForwardedPrefixHeaderName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;X-Forwarded-Prefix&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this response, we can tell that the middleware either isn’t present or has chosen to ignore the request based on its current configuration. One clear sign is that none of the &lt;strong&gt;X-Original-*&lt;/strong&gt; headers were added. These headers are only set when the middleware rewrites the request using the forwarded values, which didn’t happen here.&lt;/p&gt;
&lt;h4 id=&quot;why-not-use-the-browser&quot;&gt;&lt;strong&gt;Why not use the browser?&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;While you can use a browser to send basic requests, it won’t let you set custom headers like &lt;strong&gt;X-Forwarded-For&lt;/strong&gt; or &lt;strong&gt;X-Forwarded-Host&lt;/strong&gt;. Browsers restrict which headers can be set for security reasons. That’s why tools like &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-9.0&amp;#x26;source=docs&amp;#x26;WT.mc_id=email&amp;#x26;sharingId=MVP_427602&quot;&gt;.http&lt;/a&gt; files, &lt;a href=&quot;https://www.usebruno.com/&quot;&gt;Bruno&lt;/a&gt;, or &lt;a href=&quot;https://getfiddler.com&quot;&gt;Fiddler&lt;/a&gt; are much more suitable for this kind of testing. Because they let you define exactly which headers to include in your request.&lt;/p&gt;
&lt;h4 id=&quot;what-about-the-x-forwarded-prefix-header&quot;&gt;What about the X-Forwarded-Prefix header?&lt;/h4&gt;
&lt;p&gt;This header deserves a blog post of its own. It’s closely tied to how PathBase works in ASP.NET Core, and things can get tricky when you’re running apps behind reverse proxies. If you want to dig deeper into how PathBase works, I recommend Andrew Lock’s excellent post: &lt;a href=&quot;https://andrewlock.net/understanding-pathbase-in-aspnetcore/&quot;&gt;Understanding PathBase in ASP.NET Core&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;adding-the-forwardheaders-middleware&quot;&gt;Adding the ForwardHeaders Middleware&lt;/h2&gt;
&lt;p&gt;To start using the middleware, you first need to add it to the start of the request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseForwardedHeaders&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But just adding it like this won’t do anything on its own. You also need to configure it. Here’s the starting point:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Configure&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForwardedHeadersOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //TODO: Add configuration here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run the application now and send a test request, the output will look exactly the same as before. Nothing has changed yet.&lt;/p&gt;
&lt;h2 id=&quot;configuring-forwardheadersmiddleware&quot;&gt;Configuring ForwardHeadersMiddleware&lt;/h2&gt;
&lt;p&gt;By default, the middleware does not process any &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers. We can confirm this by checking the response, which shows an empty &lt;strong&gt;ForwardedHeaders&lt;/strong&gt; list:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;ForwardedHeaders&lt;/strong&gt; array is a core part of the middleware’s configuration. It lists which &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers the middleware is currently set up to process. Right now, it’s empty, so no headers will be considered or used when rewriting the request.&lt;/p&gt;
&lt;p&gt;We need to tell the middleware which headers it should read explicitly to fix this. The available headers are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-For&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-Host&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-Proto&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;X-Forwarded-Prefix&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some proxies use different header names. If that’s the case, you can change the expected names using these configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ForwardedForHeaderName&lt;/strong&gt; (Default “X-Forwarded-For”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ForwardedHostHeaderName&lt;/strong&gt; (Default “X-Forwarded-Host”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ForwardedProtoHeaderName&lt;/strong&gt; (Default “X-Forwarded-Proto”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ForwardedPrefixHeaderName&lt;/strong&gt; (Default “X-Forwarded-Prefix”)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To enable all four headers, configure the middleware like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ForwardedHeaders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ForwardedHeaders.XForwardedFor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                           ForwardedHeaders.XForwardedHost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                           ForwardedHeaders.XForwardedProto &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                           ForwardedHeaders.XForwardedPrefix;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or simply use the shortcut:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ForwardedHeaders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ForwardedHeaders.All;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both of these will enable the same functionality.&lt;/p&gt;
&lt;p&gt;In our case, we don’t want to support the &lt;strong&gt;X-Forwarded-Prefix&lt;/strong&gt; header, so we leave it out on purpose:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ForwardedHeaders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ForwardedHeaders.XForwardedFor &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                           ForwardedHeaders.XForwardedHost &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                            &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;						   ForwardedHeaders.XForwardedProto;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Being explicit about which headers you want to support is a good practice.&lt;/p&gt;
&lt;h3 id=&quot;why-do-we-need-to-configure-this&quot;&gt;Why do we need to configure this?&lt;/h3&gt;
&lt;p&gt;It gives you control over which headers the middleware should support and which ones to ignore. Which headers you need to support depends on the proxy that you use and how your infrastructure is set up. Not all proxies send the same headers; some may only use a subset. Being explicit helps avoid misconfigurations and improves security.&lt;/p&gt;
&lt;p&gt;Now restart the application and send the request again. You should now see that the &lt;strong&gt;ForwardedHeaders&lt;/strong&gt; array contains the expected headers:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   &quot;XForwardedFor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   &quot;XForwardedHost&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;   &quot;XForwardedProto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;forwardheaders-middleware-in-action&quot;&gt;ForwardHeaders Middleware in Action&lt;/h2&gt;
&lt;p&gt;With the &lt;strong&gt;ForwardedHeaders&lt;/strong&gt; option set, the middleware is now active. It will look for the headers you’ve configured and rewrite the request based on their values.&lt;/p&gt;
&lt;p&gt;Let’s compare what the request looks like before and after enabling the middleware:&lt;/p&gt;
&lt;h4 id=&quot;before&quot;&gt;Before&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Protocol&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;HTTP/1.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Scheme&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Path&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;PathBase&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;DisplayUrl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://localhost:5000/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;RemoteIpAddress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;::1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Headers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Forwarded-For&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;123.0.123.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Forwarded-Proto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Forwarded-Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nestenius.se&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;after&quot;&gt;After&lt;/h4&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Protocol&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;HTTP/1.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Scheme&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Path&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;PathBase&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nestenius.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;DisplayUrl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://nestenius.se//&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;RemoteIpAddress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;123.0.123.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Headers&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nestenius.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;X-Original-Proto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;X-Original-Host&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;X-Original-For&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;[::1]:61781&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The middleware has updated the &lt;strong&gt;Scheme&lt;/strong&gt;, &lt;strong&gt;Host&lt;/strong&gt;, and &lt;strong&gt;RemoteIpAddress&lt;/strong&gt; fields using the values from the &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers.&lt;/p&gt;
&lt;p&gt;At the same time, the original values have been preserved in the &lt;strong&gt;X-Original-*&lt;/strong&gt; headers. This lets your application know what was received and rewritten, which is helpful for diagnostics and auditing.&lt;/p&gt;
&lt;h2 id=&quot;forwardheadersmiddleware-in-production&quot;&gt;ForwardHeadersMiddleware in Production&lt;/h2&gt;
&lt;p&gt;Let’s say you take the code above and deploy it to a production environment where a reverse proxy (like &lt;strong&gt;NGINX&lt;/strong&gt;, &lt;strong&gt;Traefik&lt;/strong&gt;, or a &lt;strong&gt;load balancer&lt;/strong&gt;) sits between the users and your web application. This proxy is a separate service that forwards requests to your app and adds the appropriate &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But now… it stops working. Why?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the setup:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;ForwardedHeaders Architecture, a client and Proxy, that sends X-Forwarded-* headers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;507&quot; src=&quot;https://nestenius.se/_astro/proxy-forwarded-headers-flow-diagram-aspnet-core.B5OLggrA_75Xim.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The problem is that the middleware only trusts requests from known sources, meaning known proxies or networks. By default, it only trusts local addresses. If we look at the response from our application, we’ll see these two configuration settings that control this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&quot;KnownNetworks&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;127.0.0.1/8&quot;     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;],     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&quot;KnownProxies&quot;: &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[       &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &quot;::1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means the forwarded headers will be ignored unless the request comes from either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An address in the &lt;strong&gt;127.0.0.1/8&lt;/strong&gt; network range (IPv4 localhost)&lt;/li&gt;
&lt;li&gt;The specific address &lt;strong&gt;::1&lt;/strong&gt; (IPv6 localhost)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might wonder why the middleware doesn’t just trust all incoming addresses. Well, that would be a security risk! If any IP could forward headers, a malicious client could spoof values like the original IP address, scheme, or host, which could then break logging, access control, or other logic that depends on those details.&lt;/p&gt;
&lt;h3 id=&quot;how-the-trust-check-works-internally&quot;&gt;How the Trust Check Works Internally&lt;/h3&gt;
&lt;p&gt;If we look at the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs#L388&quot;&gt;source code&lt;/a&gt; of the Forwarded Headers Middleware, we find a method that decides whether the IP address of the incoming request is allowed:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CheckKnownAddress&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IPAddress&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; address&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (address.IsIPv4MappedToIPv6)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ipv4Address&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; address.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapToIPv4&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CheckKnownAddress&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ipv4Address))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (_options.KnownProxies.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Contains&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(address))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; network&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _options.KnownNetworks)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (network.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Contains&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(address))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code shows that a request is trusted if its IP address matches an entry in the &lt;strong&gt;KnownProxies&lt;/strong&gt; list or falls within a configured &lt;strong&gt;KnownNetworks&lt;/strong&gt; range.&lt;/p&gt;
&lt;h3 id=&quot;testing-without-a-real-proxy&quot;&gt;Testing Without a Real Proxy&lt;/h3&gt;
&lt;p&gt;How can we test the proxy’s behavior without setting up an entire proxy environment?&lt;/p&gt;
&lt;p&gt;One way is to fake it. You can add a simple middleware before &lt;strong&gt;UseForwardedHeaders&lt;/strong&gt; that overrides the &lt;strong&gt;RemoteIpAddress&lt;/strong&gt;. This lets you simulate a request coming from a specific proxy IP address. Here’s how that looks:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Spoof RemoteIpAddress      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	context.Connection.RemoteIpAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; IPAddress.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;10.0.0.2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseForwardedHeaders&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you now send a test request to the application, it will ignore the &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers because the spoofed IP address (&lt;strong&gt;10.0.0.2&lt;/strong&gt;) is not on the list of trusted proxies or networks.&lt;/p&gt;
&lt;h2 id=&quot;observing-the-problem-in-the-logs&quot;&gt;Observing the Problem in the Logs&lt;/h2&gt;
&lt;p&gt;When the Forwarded Headers Middleware rejects a request, it will write a debug entry to the log. It can also emit warnings for other issues, such as malformed headers or configuration problems. You need to increase the log level for the middleware’s namespace to see all of these messages.&lt;/p&gt;
&lt;p&gt;In your &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-9.0&amp;#x26;WT.mc_id=email&amp;#x26;sharingId=MVP_427602&quot;&gt;appsettings.json&lt;/a&gt;, update the logging section like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Logging&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;LogLevel&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Default&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Information&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Microsoft.AspNetCore&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Warning&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Microsoft.AspNetCore.HttpOverrides&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Debug&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;AllowedHosts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key part is setting the log level for the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.httpoverrides?view=aspnetcore-9.0&amp;#x26;WT.mc_id=email&amp;#x26;sharingId=MVP_427602&quot;&gt;&lt;strong&gt;Microsoft.AspNetCore.HttpOverrides&lt;/strong&gt;&lt;/a&gt; namespace to &lt;strong&gt;Debug&lt;/strong&gt;.This enables more logging from the Forwarded Headers Middleware.&lt;/p&gt;
&lt;p&gt;Now, if we run the application with the spoofed IP address in place, we should see a message like this in the console:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;dbug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware[1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      Unknown proxy&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;10.0.0.2:65529&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirms that the forwarded headers were ignored because the request did not come from a trusted source.&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-unknown-proxy-problem&quot;&gt;Fixing the Unknown proxy problem&lt;/h2&gt;
&lt;p&gt;To fix the issue, we need to tell the middleware which proxies or networks we trust. In our example, the proxy uses the IP address 10.0.0.2.&lt;/p&gt;
&lt;p&gt;We have two options:&lt;/p&gt;
&lt;h4 id=&quot;trust-a-specific-proxy-ip&quot;&gt;Trust a specific proxy IP&lt;/h4&gt;
&lt;p&gt;To trust just one IP address, you can configure it like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.KnownProxies.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Clear&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.KnownProxies.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(IPAddress.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;10.0.0.2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we clear the list to remove the default entry for localhost (::1). If you’re not using localhost in production, there’s no reason to keep it trusted.&lt;/p&gt;
&lt;h4 id=&quot;trust-a-network-range&quot;&gt;Trust a network range&lt;/h4&gt;
&lt;p&gt;If your proxy could use different IPs within a subnet, it’s better to trust a full network range:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.KnownNetworks.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Clear&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.KnownNetworks.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IPNetwork&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(IPAddress.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;10.0.0.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;24&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows traffic from the entire &lt;strong&gt;10.0.0.0/24&lt;/strong&gt; range (&lt;strong&gt;10.0.0.0&lt;/strong&gt; to &lt;strong&gt;10.0.0.255&lt;/strong&gt;) to be trusted.&lt;/p&gt;
&lt;p&gt;By adding either of these configurations, the middleware will now accept forwarded headers from your proxy at &lt;strong&gt;10.0.0.2&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Restricting where these headers are accepted from really is critical. Trusting all of the sources would allow clients to spoof &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers, which could affect security, logging, or routing behavior.&lt;/p&gt;
&lt;h2 id=&quot;allowed-forwarded-headers-hosts&quot;&gt;Allowed Forwarded Headers Hosts&lt;/h2&gt;
&lt;p&gt;Another important part of the Forwarded Headers Middleware is how it handles the &lt;strong&gt;X-Forwarded-Host&lt;/strong&gt; header.&lt;/p&gt;
&lt;p&gt;By default, the middleware will accept any value in the &lt;strong&gt;X-Forwarded-Host&lt;/strong&gt; header. This means that a client or proxy could send in any hostname, and the middleware would apply it without restrictions.&lt;/p&gt;
&lt;p&gt;To tighten this risky behavior, you can use the &lt;strong&gt;AllowedHosts&lt;/strong&gt; setting in the &lt;strong&gt;ForwardedHeadersOptions&lt;/strong&gt; configuration.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.AllowedHosts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Clear&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.AllowedHosts.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nestenius.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this configuration, only forwarded requests that include &lt;strong&gt;X-Forwarded-Host: nestenius.se&lt;/strong&gt; will be accepted and applied. Any other value will be ignored.&lt;/p&gt;
&lt;h2 id=&quot;the-aspnetcore_forwardedheaders_enabled-variable&quot;&gt;The ASPNETCORE_FORWARDEDHEADERS_ENABLED variable&lt;/h2&gt;
&lt;p&gt;If you’ve worked with ASP.NET Core in &lt;strong&gt;Azure App Service&lt;/strong&gt; or &lt;strong&gt;Azure Container Apps&lt;/strong&gt;, you might have encountered the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; environment variable.&lt;/p&gt;
&lt;p&gt;This environment variable controls whether ASP.NET Core will automatically add the Forwarded Headers Middleware to your application during startup.&lt;/p&gt;
&lt;p&gt;Like when you use the default host builder like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; WebApplication.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(args);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; is set to true, ASP.NET Core will automatically call &lt;strong&gt;UseForwardedHeaders()&lt;/strong&gt; behind the scenes. You don’t have to add it manually to the middleware pipeline.&lt;/p&gt;
&lt;p&gt;This behavior is implemented in a component called &lt;strong&gt;ForwardedHeadersStartupFilter&lt;/strong&gt; (&lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/ForwardedHeadersStartupFilter.cs&quot;&gt;source code&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Okay, so let’s explore this next!&lt;/p&gt;
&lt;h3 id=&quot;testing-aspnetcore_forwardedheaders_enabled&quot;&gt;Testing ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/h3&gt;
&lt;p&gt;We will remove the manual middleware setup from the application to test this feature.&lt;br&gt;
Comment out these two lines:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//builder.Services.Configure&amp;#x3C;ForwardedHeadersOptions&gt;(options =&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//    //TODO: Add configuration here... &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//}); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//app.UseForwardedHeaders();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you send a test request with &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers, they should be ignored. This is to be expected, since the middleware is no longer active.&lt;/p&gt;
&lt;h3 id=&quot;enabling-the-middleware-via-environment-variable&quot;&gt;Enabling the Middleware via Environment Variable&lt;/h3&gt;
&lt;p&gt;Next, let’s enable it using the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; environment variable. The easiest way to do that is by adding it to your &lt;strong&gt;launchSettings.json&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;https&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;commandName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Project&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;dotnetRunMessages&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;launchBrowser&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;applicationUrl&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://localhost:5001;http://localhost:5000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;environmentVariables&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;ASPNETCORE_ENVIRONMENT&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Development&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;ASPNETCORE_FORWARDEDHEADERS_ENABLED&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-forwardedheaders_enabled-setting&quot;&gt;The FORWARDEDHEADERS_ENABLED setting&lt;/h4&gt;
&lt;p&gt;If you’d prefer not to use environment variables, you can enable the Forwarded Headers Middleware by setting the &lt;strong&gt;FORWARDEDHEADERS_ENABLED&lt;/strong&gt; flag to true in your &lt;strong&gt;appsettings.json&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;  ...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;FORWARDEDHEADERS_ENABLED&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-spoofing-the-ip-no-longer-works&quot;&gt;Why Spoofing the IP No Longer Works&lt;/h4&gt;
&lt;p&gt;If you now try to spoof the remote IP address with a middleware like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // Spoof RemoteIpAddress      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	context.Connection.RemoteIpAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; IPAddress.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;10.0.0.2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);     &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	await&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(); &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…it won’t work as expected.&lt;/p&gt;
&lt;p&gt;The Forwarded Headers Middleware is already executed before this custom middleware. When you enable it using the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; environment variable, it’s added automatically at the very start of the pipeline by the internal &lt;strong&gt;ForwardedHeadersStartupFilter&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;By the time your spoofing code runs, the middleware has already processed the request using the original &lt;strong&gt;RemoteIpAddress&lt;/strong&gt;. So, changing it afterward has no effect. The forwarded headers will be ignored if the request appears to come from an untrusted IP (like &lt;strong&gt;::1&lt;/strong&gt; or &lt;strong&gt;127.0.0.1&lt;/strong&gt;), and you won’t be able to simulate a proxy this way.&lt;/p&gt;
&lt;h3 id=&quot;custom-configuration-with-forwardedheaders_enabled&quot;&gt;Custom Configuration with FORWARDEDHEADERS_ENABLED&lt;/h3&gt;
&lt;p&gt;When the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; environment variable is set to true, or the &lt;strong&gt;FORWARDEDHEADERS_ENABLED&lt;/strong&gt; setting is enabled in your configuration, the Forwarded Headers Middleware is automatically added at the beginning of the request pipeline.&lt;/p&gt;
&lt;p&gt;By default, this middleware uses a built-in configuration that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Processes the &lt;strong&gt;X-Forwarded-For&lt;/strong&gt; and &lt;strong&gt;X-Forwarded-Proto&lt;/strong&gt; headers&lt;/li&gt;
&lt;li&gt;Clears the list of trusted proxies and networks (meaning none are trusted)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This behavior comes from the internal &lt;strong&gt;ForwardedHeadersOptionsSetup&lt;/strong&gt; class, which you can find here: &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs&quot;&gt;ForwardedHeadersOptionsSetup&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;overriding-the-default-configuration&quot;&gt;Overriding the Default Configuration&lt;/h4&gt;
&lt;p&gt;To override this behavior, you can manually configure the options in your &lt;strong&gt;Program&lt;/strong&gt; class:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Configure&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForwardedHeadersOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ForwardedHeaders &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ForwardedHeaders.All;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.KnownProxies.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Clear&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.KnownProxies.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(IPAddress.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;10.0.0.2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;using-appsettingsjson&quot;&gt;Using appsettings.json &lt;/h4&gt;
&lt;p&gt;You can also use &lt;strong&gt;appsettings.json&lt;/strong&gt; to configure the middleware:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;XForwardedFor,XForwardedHost,XForwardedProto&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;ForwardLimit&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;RequireHeaderSymmetry&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;KnownProxies&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;::1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;KnownNetworks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;127.0.0.1/8&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;AllowedHosts&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;nestenius.se&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To apply this configuration, you still need to bind it in code using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.Configure&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ForwardedHeadersOptions&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            (builder.Configuration.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetSection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ForwardedHeaders&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without this line, the settings from &lt;strong&gt;appsettings.json&lt;/strong&gt; will be ignored, and the middleware will fall back to its built-in defaults.&lt;/p&gt;
&lt;h2 id=&quot;forwarded-headers-in-the-cloud-debugger&quot;&gt;Forwarded Headers in the Cloud Debugger&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;Cloud Debugger&lt;/a&gt; is the perfect tool for exploring how forwarded headers are used in practice, especially in environments like Azure App Services or Azure Container Apps.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;Cloud Debugger&lt;/a&gt; is an open-source tool I created using ASP.NET Core 9. It’s designed to help developers, architects, and DevOps teams inspect, explore, and debug HTTP requests and Azure-specific behaviors in live cloud deployments.&lt;/p&gt;
&lt;p&gt;You can deploy it directly to Azure as an &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/app-service/overview?WT.mc_id=email&amp;#x26;sharingId=MVP_427602&quot;&gt;App Service&lt;/a&gt; or &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/container-apps/overview?WT.mc_id=email&amp;#x26;sharingId=MVP_427602&quot;&gt;Container App&lt;/a&gt;. Once it’s running, it provides a browser-based interface with several built-in tools to inspect HTTP headers, understand how forwarded headers are handled, and explore its middleware configurations tools.&lt;/p&gt;
&lt;h2 id=&quot;source-code&quot;&gt;Source Code&lt;/h2&gt;
&lt;p&gt;You can find the source code related to this blog post on &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;GitHub.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Writing this blog post has been a deep and practical journey into the world of &lt;strong&gt;proxies&lt;/strong&gt;, &lt;strong&gt;forwarded headers,&lt;/strong&gt; and how &lt;strong&gt;ASP.NET Core&lt;/strong&gt; handles these scenarios under the hood.&lt;/p&gt;
&lt;p&gt;We started with a minimal app to explore how the middleware works in a controlled environment. Then, we stepped through configuration options, common pitfalls, and production considerations like trusted proxies and forwarded host validation. We also looked at how to test proxy scenarios locally, how logging can help when things don’t behave as expected, and what happens when you enable the middleware using the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; environment variable.&lt;/p&gt;
&lt;p&gt;Even though we covered a lot, there are still more advanced topics to explore! Topics like support for multiple proxy layers, deeper integration with Kubernetes ingress controllers, and the details of how PathBase actually interacts with X-Forwarded-Prefix.&lt;/p&gt;
&lt;p&gt;If you’re deploying applications behind a reverse proxy or load balancer, you can consider getting familiar with this middleware as time well spent!&lt;/p&gt;
&lt;p&gt;And don’t forget, if you want to explore how Azure handles forwarded headers in real-world environments, check out the open-source &lt;a href=&quot;https://github.com/tndata/CloudDebugger/&quot;&gt;Cloud Debugger&lt;/a&gt;. It’s a practical tool for testing, learning, and debugging how ASP.NET Core behaves when it’s deployed behind Azure front ends.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://devblogs.microsoft.com/dotnet/forwarded-headers-middleware-updates-in-net-core-3-0-preview-6/&quot;&gt;Forwarded Headers Middleware Updates in .NET Core 3.0 preview 6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-For&quot;&gt;X-Forwarded-For&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soapfault.com/2020/02/24/asp-net-core-reverse-proxy-and-x-forwarded-headers/&quot;&gt;Asp.Net Core, reverse proxy, and X-Forwarded-* headers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-9.0&amp;#x26;WT.mc_id=email&amp;#x26;sharingId=MVP_427602&quot;&gt;Configure ASP.NET Core to work with proxy servers and load balancers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;Exploring the Forwarded Headers Middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Introducing the Coding Agent Explorer (.NET)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;p&gt;Writing this blog post has been a deep and practical journey into the world of proxies, forwarded headers, and how ASP.NET Core handles these scenarios under the hood.&lt;/p&gt;
&lt;p&gt;We started with a minimal app to explore how the middleware works in a controlled environment. Then, we stepped through configuration options, common pitfalls, and production considerations like trusted proxies and forwarded host validation. We also looked at how to test proxy scenarios locally, how logging can help when things don’t behave as expected, and what happens when you enable the middleware using the &lt;strong&gt;ASPNETCORE_FORWARDEDHEADERS_ENABLED&lt;/strong&gt; environment variable.&lt;/p&gt;
&lt;p&gt;Even though we covered a lot, there are still more advanced topics to explore! Topics like support for &lt;strong&gt;multiple proxy layers&lt;/strong&gt;, &lt;strong&gt;deeper integration with Kubernetes ingress controllers&lt;/strong&gt;, and the details of how &lt;strong&gt;PathBase&lt;/strong&gt; actually interacts with &lt;strong&gt;X-Forwarded-Prefix&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you’re deploying applications behind a reverse proxy or load balancer, you can consider getting familiar with this middleware as time well spent!&lt;/p&gt;
&lt;p&gt;And don’t forget, if you want to explore how Azure handles forwarded headers in real-world environments, check out the open-source &lt;a href=&quot;https://github.com/tndata/CloudDebugger/&quot;&gt;Cloud Debugger&lt;/a&gt;. It’s a practical tool for testing, learning, and debugging how ASP.NET Core behaves when it’s deployed behind Azure front ends.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web Security Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category></item><item><title>Exploring the Forwarded Headers Middleware in ASP.NET Core</title><link>https://nestenius.se/net/exploring-the-forwarded-headers-middleware-in-asp-net-core/</link><guid isPermaLink="true">https://nestenius.se/net/exploring-the-forwarded-headers-middleware-in-asp-net-core/</guid><description>Proxies are vital for load balancing and security, but they obscure the actual client IP, scheme, and domain, causing broken links, inaccurate logging</description><pubDate>Wed, 22 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Proxies are vital for load balancing and security, but they obscure the actual client IP, scheme, and domain, causing broken links, inaccurate logging, and other headaches. In this post, we’ll look at how ASP.NET Core’s &lt;strong&gt;Forwarded Headers Middleware&lt;/strong&gt; restores these details so your services behave as though they’re directly on the public internet.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-forwarded-headers-middleware&quot;&gt;What is the Forwarded Headers Middleware?&lt;/h2&gt;
&lt;p&gt;In ASP.NET Core, requests travel through a pipeline made of various middleware components. The &lt;strong&gt;Forwarded Headers Middleware&lt;/strong&gt; is one such component that often comes pre-configured (or can be easily enabled) to handle forwarded headers from proxies. It’s typically placed among the first modules in the pipeline, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of the ASP.NET Core request pipeline with the Forwarded Headers middleware&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;740&quot; height=&quot;215&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-kestrel-forwarded-headers-middleware-pipeline.B682dSO8_ZjUtbE.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-problem-does-the-forwarded-headers-middleware-solve&quot;&gt;What problem does the Forwarded Headers Middleware solve?&lt;/h2&gt;
&lt;p&gt;In many scenarios, our services and applications are deployed behind a proxy server, which may serve various roles, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load balancer&lt;/li&gt;
&lt;li&gt;Web Application Firewall (WAF)&lt;/li&gt;
&lt;li&gt;Reverse proxy&lt;/li&gt;
&lt;li&gt;Content cache&lt;/li&gt;
&lt;li&gt;TLS Termination&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The example below shows three service instances behind a proxy, acting as a load balancer.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;three service instances behind a proxy, acting as a load balancer.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;973&quot; height=&quot;279&quot; src=&quot;https://nestenius.se/_astro/client-https-request-load-balancer-proxy-three-service-insta.COVU7lXu_MAX8Y.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The general problem is that the details about the original request are lost as it passes through the proxy.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Shows how the internal service belives it is hosted as http://service1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;996&quot; height=&quot;332&quot; src=&quot;https://nestenius.se/_astro/load-balancer-proxy-distributing-https-traffic-to-three-serv.CqA12S9v_Z2nj0oc.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In other scenarios, a single proxy might manage traffic for multiple domain names, and the services need to identify the original request to return the correct content to the caller.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How a proxy can have multiple public domains&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;984&quot; height=&quot;294&quot; src=&quot;https://nestenius.se/_astro/load-balancer-proxy-routing-multiple-domains-to-services.DgD-zLZy_6J2Oi.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;making-aspnet-core-services-proxy-aware&quot;&gt;Making ASP.NET Core Services Proxy-Aware&lt;/h2&gt;
&lt;p&gt;The goal is to make the internal services act as though they are directly accessible on the public internet, even though they are behind a proxy.&lt;/p&gt;
&lt;p&gt;To solve this, we somehow need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recognize the public URL that the user accessed instead of the internal network address.&lt;/li&gt;
&lt;li&gt;Identify the original client IP address rather than the IP address of the proxy.&lt;/li&gt;
&lt;li&gt;Determine the scheme of the original request (HTTP or HTTPS).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As shown below, we want the application in &lt;strong&gt;service1&lt;/strong&gt; to believe it received a request to &lt;strong&gt;&lt;a href=&quot;https://tn-data.se&quot;&gt;https://tn-data.se&lt;/a&gt;&lt;/strong&gt; rather than &lt;strong&gt;&lt;a href=&quot;http://service1&quot;&gt;http://service1&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How we want the service to belive it is hosted on the public internet&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;992&quot; height=&quot;343&quot; src=&quot;https://nestenius.se/_astro/load-balancer-proxy-routing-https-to-multiple-service-instan.B2guK7RF_Z2gQgBm.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;lets-get-a-bit-more-technical&quot;&gt;Let’s get a bit more technical&lt;/h2&gt;
&lt;p&gt;Let’s dive a bit deeper into the technical details. Imagine a setup with a proxy sitting between the client and the service:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;example setup of a proxy and a service&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;357&quot; src=&quot;https://nestenius.se/_astro/load-balancer-proxy-obscuring-client-ip-scheme-aspnet-core.D_pmXtAE_tj3iH.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;How can we make &lt;strong&gt;Service1&lt;/strong&gt; behave as if it received a request from IP &lt;strong&gt;193.8.1.12&lt;/strong&gt; to &lt;strong&gt;&lt;a href=&quot;https://tn-data.se&quot;&gt;https://tn-data.se&lt;/a&gt;&lt;/strong&gt; instead of IP &lt;strong&gt;10.0.0.1&lt;/strong&gt; to &lt;strong&gt;&lt;a href=&quot;http://service1&quot;&gt;http://service1&lt;/a&gt;&lt;/strong&gt;?&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-x-forwarded-headers&quot;&gt;Introducing the X-Forwarded Headers&lt;/h2&gt;
&lt;p&gt;The first step is configuring the proxy to add a set of headers to the outgoing request describing the original request received by the proxy.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Hows how the proxy sets the x-forwarded-* headers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;477&quot; src=&quot;https://nestenius.se/_astro/load-balancer-proxy-x-forwarded-headers-flow.D2jSx953_Z1c8M1T.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers are special HTTP headers that help servers understand where a request came from when it passes through intermediaries like proxies or load balancers. They provide essential details such as the original client’s IP address, the protocol used, and the host requested.&lt;/p&gt;
&lt;p&gt;Proxies can add these headers to the request, informing the service about the originating IP, scheme, and &lt;strong&gt;host&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let’s explore how we handle this in ASP.NET Core next.&lt;/p&gt;
&lt;h2 id=&quot;aspnet-core-and-the-x-forwarded-headers&quot;&gt;ASP.NET Core and the X-Forwarded Headers&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Forwarded Headers Middleware&lt;/strong&gt; is responsible for rewriting the request when these headers are present in the incoming request.&lt;/p&gt;
&lt;p&gt;Using the details from the above example, here’s what happens to the request as it passes through the middleware:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the forwarded headers middleware rewrites the request&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;934&quot; height=&quot;311&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-forwarded-headers-middleware-ip-scheme-host-swap.C-X8Y0vl_Z6ImoL.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Steps taken:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The middleware adds &lt;strong&gt;X-Original-*&lt;/strong&gt; headers to the request, containing details about the current request.&lt;/li&gt;
&lt;li&gt;It then rewrites the request based on the information found in the &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;forwarded-headers-in-azure-container-apps&quot;&gt;Forwarded Headers in Azure Container Apps&lt;/h2&gt;
&lt;p&gt;To observe these headers in action, we can deploy my open-source &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;Cloud Debugger&lt;/a&gt; tool as an Azure Container App. This tool allows us to inspect the request before and after this middleware has processed it.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example from the Cloud Debugger showing the raw and final reuest details&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;302&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-forwarded-headers-request-details-azure-containe.CnAFVE-4_CY7fL.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;For example, in the image above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The request scheme has been updated from &lt;strong&gt;HTTP&lt;/strong&gt; to &lt;strong&gt;HTTPS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The sender’s IP address has changed from &lt;strong&gt;::ffff:100.100.0.60&lt;/strong&gt; (a local IPv6 Azure address) to &lt;strong&gt;193.150.241.131&lt;/strong&gt; (my computer’s public IP address).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;::ffff:x.x.x.x&lt;/strong&gt; format is an IPv6 representation of an IPv4 address, mapping it into the IPv6 space.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This shows how the middleware reconstructs the original request context using the &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers.&lt;/p&gt;
&lt;p&gt;In the cloud debugger for Azure, we also get this nice overview of the headers:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example from the Cloud Debugger, comparing the headers before and after.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;891&quot; height=&quot;304&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-request-header-differences-forwarded-vs-original.CnVewLJ7_ZxD3RY.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here, two &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers are received, and two corresponding &lt;strong&gt;X-Original-*&lt;/strong&gt; headers are created with the updated information.&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Black and white professional headshot of a man in a suit jacket&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-about-security&quot;&gt;What About Security?&lt;/h2&gt;
&lt;p&gt;The Forwarded Headers Middleware offers several ways to validate and control how &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers are processed. Because blindly trusting them can be risky. Attackers or misconfigured proxies might send bogus headers, leading to incorrect IP addresses, spoofed schemes, and other vulnerabilities. In a future post, we’ll explore best practices to secure and configure these headers properly, including limiting trusted networks and carefully validating header values.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this blog post, I examined the purpose and functionality of the Forwarded Headers Middleware in ASP.NET Core. This middleware processes &lt;strong&gt;X-Forwarded-*&lt;/strong&gt; headers to restore information about the original request, including the client’s IP address, the request scheme (HTTP or HTTPS), and the public URL. The middleware modifies the request with these details while preserving the original values in dedicated headers for reference.&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt; post, I’ll provide a hands-on guide to configuring and working with these headers in your applications.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer&quot;&gt;Configure ASP.NET Core to work with proxy servers and load balancers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.httpoverrides&quot;&gt;Microsoft.AspNetCore.HttpOverrides Namespace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Middleware/HttpOverrides&quot;&gt;Forwarded Headers source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7239&quot;&gt;RFC 7239: Forwarded HTTP Extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/&quot;&gt;AdditionalAuthorizationParameters in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web Security Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>AdditionalAuthorizationParameters in ASP.NET Core 9</title><link>https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/</link><guid isPermaLink="true">https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/</guid><description>A practical look at AdditionalAuthorizationParameters in ASP.NET Core 9. How it simplifies customizing OAuth/OIDC authorization requests and how it works with PAR.</description><pubDate>Mon, 30 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In &lt;strong&gt;ASP.NET Core 9&lt;/strong&gt;, a new feature called &lt;strong&gt;AdditionalAuthorizationParameters&lt;/strong&gt; allows you to customize OAuth and OpenID Connect (OIDC) flows more quickly. This new feature allows developers to add custom authentication parameters without needing to rely on the complex workarounds that existed before ASP.NET Core 9 was released. Sounds familiar? Then you’re going to like this!&lt;/p&gt;
&lt;p&gt;In this blog, I will practically introduce you to this feature, how it works, and how it ties together with Pushed Authorization Requests and AuthenticationProperties. Let’s jump in.&lt;/p&gt;
&lt;h2 id=&quot;why-do-we-need-additionalauthorizationparameters&quot;&gt;Why do we need AdditionalAuthorizationParameters?&lt;/h2&gt;
&lt;p&gt;Before ASP.NET Core 9, adding custom parameters to OAuth or OpenID Connect (OIDC) authorization requests was a cumbersome challenge. Developers had to override methods like &lt;code&gt;BuildChallengeUrl&lt;/code&gt; in custom OAuth handlers or insert parameters manually using event hooks such as &lt;code&gt;OnRedirectToIdentityProvider&lt;/code&gt;. This approach was verbose and error-prone, often leading to inconsistent project implementations.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions.additionalauthorizationparameters&quot;&gt;AdditionalAuthorizationParameters&lt;/a&gt; solves this by providing a more straightforward and maintainable approach by including custom parameters directly in the authorization request configuration.&lt;/p&gt;
&lt;h2 id=&quot;what-does-adding-custom-parameters-mean&quot;&gt;What Does Adding Custom Parameters Mean?&lt;/h2&gt;
&lt;p&gt;Customizing the initial authorization request sent to an OIDC authorization server often involves adding extra parameters to control or enhance the authentication process. A typical authorization request might look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://identityservice.secure.nu/connect/authorize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;localhost-addoidc-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid%20profile%20email%20offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;dtaF1NHSE5-C6E9eDuf-kEZJ_j7n48BaSTmezd4Go7Y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge_method=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_mode=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;form_post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638645227425598693.OGFkYjhmMWUtZ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd3_JvsHlnov...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-SKU=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ID_NET9_0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-ver=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;8.1.2.0 HTTP/1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes, you may need to add extra parameters to further customize the authentication flow, for example, adding an audience, a tenant ID, or setting a specific prompt behavior.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curious about what &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;nonce&lt;/code&gt; contain?&lt;/strong&gt; See &lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-old-way-complex-workarounds&quot;&gt;The Old Way: Complex Workarounds&lt;/h2&gt;
&lt;p&gt;Before .NET 9, adding custom parameters often forced developers to resort to convoluted methods, such as:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.OnRedirectToIdentityProvider &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        context.ProtocolMessage.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SetParameter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;parameter1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;value1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        context.ProtocolMessage.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SetParameter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;parameter2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;value2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        context.ProtocolMessage.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SetParameter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;parameter3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;value3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This results in an authorization request like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://identityservice.secure.nu/connect/authorize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;localhost-addoidc-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid%20profile%20email%20offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;dtaF1NHSE5-C6E9eDuf-kEZJ_j7n48BaSTmezd4Go7Y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge_method=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_mode=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;form_post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638645227425598693.OGFkYjhmMWUtZ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parameter1=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;value1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parameter2=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;value2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parameter3=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;value3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd3_JvsHlnov...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-SKU=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ID_NET9_0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-ver=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;8.1.2.0 HTTP/1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this approach worked, it required extra boilerplate and was far from intuitive.&lt;/p&gt;
&lt;h2 id=&quot;how-does-additionalauthorizationparameters-improve-this&quot;&gt;How Does AdditionalAuthorizationParameters Improve This?&lt;/h2&gt;
&lt;p&gt;In ASP.NET Core 9, this process is significantly simplified. The new &lt;strong&gt;AdditionalAuthorizationParameters&lt;/strong&gt; property, added to &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions&quot;&gt;OpenIdConnectOptions&lt;/a&gt;, allows developers to configure additional parameters with minimal effort:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OpenIdConnectOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RemoteAuthenticationOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;summary&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Gets the additional parameters that will be included in the authorization request.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/summary&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;remarks&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// The additional parameters can be used to customize the authorization request,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// providing extra information or fulfilling specific requirements of the&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// OpenIdConnect provider. These parameters are typically, but not always,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// appended to the query string.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/remarks&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IDictionary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AdditionalAuthorizationParameters&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Dictionary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lets you achieve the same goal with cleaner, more readable code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddMyOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.AdditionalAuthorizationParameters.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;parameter1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;value1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.AdditionalAuthorizationParameters.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;parameter2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;value2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.AdditionalAuthorizationParameters.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;parameter3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;value3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a much-needed improvement when configuring OpenID Connect, making the code more maintainable.&lt;/p&gt;
&lt;h2 id=&quot;what-about-pushed-authorization-requests-par&quot;&gt;What About Pushed Authorization Requests (PAR)?&lt;/h2&gt;
&lt;p&gt;The feature is also compatible with &lt;strong&gt;Pushed Authorization Requests (PAR)&lt;/strong&gt;. When using PAR, the request with &lt;code&gt;AdditionalAuthorizationParameters&lt;/code&gt; might look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://identityservice.nu/connect/par &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; identityservice.secure.nu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;User-Agent&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Microsoft ASP.NET Core OpenIdConnect handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/x-www-form-urlencoded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=localhost-addoidc-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid+profile+email+offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;oIDRmjhH7UJXLaGXFNxbU5obKYXk8lMfj-OSsP73DmQ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge_method=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_mode=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;form_post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638645236162573853jIyMzc5...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parameter1=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;value1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parameter2=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;value2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;parameter3=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;value3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd3_...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_secret=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mysecret&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The additional parameters are seamlessly included in the PAR request, keeping the feature compatible with modern authorization flows.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Curious about PAR?&lt;/strong&gt; See &lt;a href=&quot;https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt; for the full picture.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;additionalauthorizationparameters-vs-authenticationproperties&quot;&gt;AdditionalAuthorizationParameters vs. AuthenticationProperties&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.oauth.oauthoptions.additionalauthorizationparameters&quot;&gt;AdditionalAuthorizationParameters&lt;/a&gt; lets you append custom parameters to the &lt;em&gt;initial&lt;/em&gt; OpenID Connect authorization request.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationproperties&quot;&gt;AuthenticationProperties&lt;/a&gt; serves a different purpose: handling transient data stored alongside the authentication flow, such as redirection URLs or other metadata relevant to the sign-in process.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AdditionalAuthorizationParameters&lt;/code&gt; is a small but focused addition to ASP.NET Core 9 that fills a real gap in the OpenID Connect options model. If you need to pass custom parameters to the authorization endpoint, this is now the clean, built-in way to do it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplifies customizing the initial authorization request.&lt;/li&gt;
&lt;li&gt;Only applies to the OpenID Connect authentication handler.&lt;/li&gt;
&lt;li&gt;Compatible with Pushed Authorization Requests (PAR).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/51250&quot;&gt;API Proposal&lt;/a&gt;: The original GitHub issue related to this feature — Add &lt;code&gt;AdditionalAuthorizationParameters&lt;/code&gt; to &lt;code&gt;OAuthOptions&lt;/code&gt;/&lt;code&gt;OpenIdConnectOptions&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>openid-connect</category><category>aspnet-core</category><category>oauth</category></item><item><title>IdentityServer In Docker Containers – Handle Logout (Part 4)</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-4/</link><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-4/</guid><description>In this final post in this series, we’ll now resolve logout challenges you might run into with IdentityServer, ensure proper sign-out redirects, and</description><pubDate>Mon, 16 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this final post in this series, we’ll now resolve logout challenges you might run into with IdentityServer, ensure proper sign-out redirects, and summarize the key takeaways from the series. Let’s complete the setup and finalize our IdentityServer configuration!&lt;/p&gt;
&lt;p&gt;This blog has been broken up into four separate posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers: Adding Containers (Part 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-2/&quot;&gt;IdentityServer in Docker Containers: Networking (Part 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-3/&quot;&gt;IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IdentityServer in Docker Containers: Handle Logout (Part 4)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;logout-from-identityserver-attempt-10&quot;&gt;Logout from IdentityServer (Attempt #10)&lt;/h2&gt;
&lt;p&gt;With the client application and IdentityServer successfully set up, communicating, and allowing users to log in, it’s time to ensure that sign-outs work securely and as expected.&lt;/p&gt;
&lt;p&gt;When you run the system and log in at &lt;code&gt;https://localhost:5001/&lt;/code&gt;, you should be redirected to the IdentityServer login page at &lt;code&gt;https://localhost:7001/Account/Login&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;From there, you can log in using the credentials &lt;strong&gt;bob&lt;/strong&gt;/&lt;strong&gt;bob&lt;/strong&gt;. You’ll be redirected back to the client application after successfully logging in and granting any necessary consent.&lt;/p&gt;
&lt;p&gt;In the top-right corner, you should see the name of the logged-in user:&lt;/p&gt;
&lt;p&gt;Clicking on the &lt;strong&gt;username&lt;/strong&gt; will display detailed information about the current user, including their claims and tokens.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;What the client looks like after signing in with IdentityServer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;636&quot; height=&quot;146&quot; src=&quot;https://nestenius.se/_astro/asp-net-core-client-app-logged-in-bob-smith.CEDDQQQi_1He22e.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;Displaying the user details for the currently signed in user in ASP.NET Core.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;447&quot; height=&quot;290&quot; src=&quot;https://nestenius.se/_astro/identityserver-user-info-page-bob-smith-claims.C-1rmYnr_1FGEp1.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Additionally, this sample application provides a tool that shows all the details about the current request, such as the request headers:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The &amp;quot;View Current Request&amp;quot; viewer tool. Included in the tool that shows the request details.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;784&quot; height=&quot;400&quot; src=&quot;https://nestenius.se/_astro/client-app-view-current-request-http2-details.PLVHNk9k_Z18MDFY.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, there is still one final issue: when attempting to log out, you’re redirected to this URL:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;http://identity/connect/endsession?post%5C_logout%5C_redirect%5C_uri&quot;&gt;http://identity/connect/endsession?post\_logout\_redirect\_uri&lt;/a&gt;…&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This URL is incorrect because the identity service name is only accessible within the Docker network.&lt;/p&gt;
&lt;p&gt;Fortunately, we can fix this by adding a custom event handler to &lt;strong&gt;AddOpenIDConnect&lt;/strong&gt; to adjust the logout URL.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.OnRedirectToIdentityProviderForSignOut &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        context.ProtocolMessage.IssuerAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;https://localhost:7001/connect/endsession&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when you try to sign out, you’ll first be prompted to confirm the logout:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Duende IdentityServer logout page.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;378&quot; height=&quot;161&quot; src=&quot;https://nestenius.se/_astro/identityserver-logout-confirmation-prompt.BqLgrGYk_12kR7g.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you confirm, you’ll be presented with a screen that confirms you have successfully logged out:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;After logging out fro Duende IdentityServer page.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;514&quot; height=&quot;95&quot; src=&quot;https://nestenius.se/_astro/identityserver-logout-success-message.Bj-Mkm9__1HP4NX.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Great! You’ve successfully logged out, but we can improve the logout experience even more.&lt;/p&gt;
&lt;h2 id=&quot;identityserver-redirect-after-sign-out-attempt-11&quot;&gt;IdentityServer Redirect After Sign Out (Attempt #11)&lt;/h2&gt;
&lt;p&gt;The current sign-out experience can be improved by automatically redirecting the user back to the client application after logging out. How do we achieve this?&lt;/p&gt;
&lt;p&gt;Start by locating the &lt;strong&gt;LogoutOptions&lt;/strong&gt; class in IdentityServer, which is somewhat tucked away in the &lt;strong&gt;PagesLogout&lt;/strong&gt; folder. It would be more intuitive if this configuration were placed in a more central location, but for now, we’ll modify it as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; LogoutOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ShowLogoutPrompt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bool&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AutomaticRedirectAfterSignOut&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, after restarting the application, nothing has changed! That’s odd. How do we solve this?&lt;/p&gt;
&lt;h3 id=&quot;jwt-token-validation-error-idx10205-issuer-validation-failed&quot;&gt;JWT token validation error: IDX10205: Issuer validation failed.&lt;/h3&gt;
&lt;p&gt;Checking the IdentityServer logs, you should see this error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;JWT token validation error: IDX10205: Issuer validation failed. Issuer: &apos;http://identity&apos;. &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Did not match: validationParameters.ValidIssuer: &apos;https://localhost:7001&apos; or &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;validationParameters.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ValidIssuers: &apos;null&apos; or validationParameters.ConfigurationManager.CurrentConfiguration.Issuer: &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&apos;Null&apos;. For more details, see https://aka.ms/IdentityModel/issuer-validation.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait, why is there a token issue when we sign out? We’re not calling an API? When the client requests IdentityServer to sign out, it sends a request like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Request starting HTTP/2 GET https://localhost:7001/connect/endsession&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A5001%2Fsignout-callback-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNEODRGQTgzNEY...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;state=CfDJ8KWm8...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;x-client-SKU=ID_NET8_0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;x-client-ver=7.1.2.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Black and white professional headshot of a man in a dark blazer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-id_token_hint&quot;&gt;What is the Purpose of id_token_hint?&lt;/h2&gt;
&lt;p&gt;According to the &lt;a href=&quot;https://openid.net/specs/openid-connect-rpinitiated-1_0.html&quot;&gt;OpenID Connect specification&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The ID Token previously issued by the OP to the RP is passed to the Logout Endpoint as a hint about the end user’s current authenticated session with the Client. This indicates the end-user’s identity that the RP requests to be logged out by the OP.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This token serves several important purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Session Identification&lt;/strong&gt;:&lt;br&gt;
This helps the Identity Provider identify which session to log out of, especially if multiple sessions are active.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logout Context&lt;/strong&gt;:&lt;br&gt;
Ensures the logout request is associated with a valid session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;:&lt;br&gt;
The token mitigates risks like Cross-Site Request Forgery (CSRF) attacks by verifying that the request comes from a trusted client.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User Experience&lt;/strong&gt;:&lt;br&gt;
This improves the flow by allowing the Identity Provider to make informed decisions about logging out of specific clients or all sessions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;whats-inside-the-id-token&quot;&gt;What’s Inside the ID Token?&lt;/h3&gt;
&lt;p&gt;If you decode the &lt;strong&gt;id_token&lt;/strong&gt; using &lt;a href=&quot;https://jwt.io&quot;&gt;&lt;strong&gt;https://jwt.io&lt;/strong&gt;&lt;/a&gt;, you’ll see something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;nbf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1728670667&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;iat&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1728670667&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;exp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1728670967&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;localhost-addoidc-client&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;amr&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;pwd&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;nonce&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;638642674649534072.ZDJlZWY0YzAtZjdmMi00ZTQ0LWIxZTAtOTM1ZjBiMThkMDNkZTM0ZTMxM2UtOGQ3My00ZTZlLTk3ZTktYWZhNDBhMmExOTVk&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;at_hash&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;kGT-YFIlnrw1AsguWas2yw&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;sid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;F410E3EB5B7B5B0E6A8C8F3AC16BBA8F&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;sub&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;auth_time&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1728670667&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;idp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;local&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bob Smith&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;given_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;family_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Smith&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BobSmith@email.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;email_verified&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;website&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://bob.com&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is that the token’s issuer is &lt;strong&gt;&lt;a href=&quot;http://identity&quot;&gt;http://identity&lt;/a&gt;&lt;/strong&gt;, while IdentityServer expects it to be &lt;code&gt;https://localhost:7001&lt;/code&gt;. This discrepancy stems from the dynamic discovery document issue we encountered earlier.&lt;/p&gt;
&lt;h2 id=&quot;how-to-fix-the-issuer-in-identityserver&quot;&gt;How to fix the issuer in IdentityServer&lt;/h2&gt;
&lt;p&gt;To fix this, we can set a static issuer in &lt;strong&gt;IdentityServer&lt;/strong&gt; by adding the following to its &lt;strong&gt;Program.cs&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; isBuilder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.IssuerUri &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://identity&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This change ensures that the tokens issued have a consistent issuer value no matter where the request originates. For example, with this change, the discovery document will contain:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Within Docker&lt;/strong&gt; (&lt;a href=&quot;http://identity/.well-known/openid-configuration&quot;&gt;http://identity/.well-known/openid-configuration&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   &quot;issuer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   &quot;jwks_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity/.well-known/openid-configuration/jwks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   &quot;authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity/connect/authorize&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;   ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;From the Host&lt;/strong&gt; (&lt;code&gt;https://localhost:7001/.well-known/openid-configuration&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   &quot;issuer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identity&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   &quot;jwks_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://localhost:7001/.well-known/openid-configuration/jwks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;   &quot;authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://localhost:7001/connect/authorize&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;   ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By setting the &lt;strong&gt;IssuerUri&lt;/strong&gt; to a static value (&lt;strong&gt;&lt;a href=&quot;https://identity&quot;&gt;https://identity&lt;/a&gt;&lt;/strong&gt;), we ensure the issuer remains consistent across all environments while the other parameters adjust based on the request’s origin.&lt;/p&gt;
&lt;p&gt;When we try to sign out now, we are automatically redirected back to the client application.&lt;/p&gt;
&lt;h2 id=&quot;working-with-proxies&quot;&gt;Working with proxies?&lt;/h2&gt;
&lt;p&gt;If your client is behind a proxy or load balancer, you might need to configure the ForwardedHeaders middleware. Check out these two blog posts for more details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-the-forwarded-headers-middleware-in-asp-net-core/&quot;&gt;Exploring the Forwarded Headers Middleware in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary-and-feedback&quot;&gt;Summary and Feedback&lt;/h2&gt;
&lt;p&gt;Writing this blog series has been both challenging and rewarding. Learning how to containerize IdentityServer and connect it with a client application in Docker taught me a lot about IdentityServer and OpenID Connect, even some details I didn’t fully understand.&lt;/p&gt;
&lt;p&gt;I might have made mistakes or missed some important points. I would love to hear your feedback and suggestions on improving these posts. Your thoughts will help me make this content better for everyone.&lt;/p&gt;
&lt;p&gt;We also deliberately chose not to modify the host file, which could have resolved some issues faster. However, taking the harder route allowed us to learn more and better understand the process.&lt;/p&gt;
&lt;h2 id=&quot;source-code&quot;&gt;Source Code&lt;/h2&gt;
&lt;p&gt;You can find all the source code for each step, along with the final solution, on my &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;&lt;strong&gt;GitHub repository&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;whats-next-for-me&quot;&gt;What’s next for me?&lt;/h2&gt;
&lt;p&gt;I’m planning to turn this blog series into a live webinar. If you’re interested in learning more about IdentityServer and want to join an interactive session, sign up for my newsletter to stay updated on the event details.&lt;/p&gt;
&lt;h2 id=&quot;what-should-i-blog-about-next&quot;&gt;What Should I Blog About Next?&lt;/h2&gt;
&lt;p&gt;I’m always looking to create content that helps the developer community. What topics would you like me to cover in future blog posts? Your suggestions are invaluable, and I’d love to hear your ideas! You can find my contact details &lt;a href=&quot;//contact/&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;do-you-need-help-with-authentication&quot;&gt;Do You Need Help with Authentication?&lt;/h2&gt;
&lt;p&gt;I offer training and consulting services to help teams implement secure and scalable authentication solutions, including IdentityServer and OpenID Connect. I also specialize in ASP.NET Core development, architecture design, and code reviews.&lt;/p&gt;
&lt;p&gt;Feel free to &lt;a href=&quot;https://tn-data.se/&quot;&gt;&lt;strong&gt;reach out&lt;/strong&gt;&lt;/a&gt; if you need support with authentication, ASP.NET Core, or architectural guidance. I’m here to help you build robust and secure solutions.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-identityserver-and-openid-connect/&quot;&gt;Introduction to IdentityServer and OpenID-Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>identityserver</category><category>openid-connect</category></item><item><title>IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-3/</link><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-3/</guid><description>In this third part of the series, we tackle login issues in IdentityServer caused by cookie restrictions in HTTP and show how to resolve them by</description><pubDate>Sun, 08 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this third part of the series, we tackle login issues in IdentityServer caused by cookie restrictions in HTTP and show how to resolve them by implementing HTTPS. We’ll guide you through securing communication between the host, client, and IdentityServer containers and configuring HTTPS in Docker to ensure everything runs smoothly.&lt;/p&gt;
&lt;p&gt;This blog has been broken up into four separate posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers: Adding Containers (Part 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-2&quot;&gt;IdentityServer in Docker Containers: Networking (Part 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-4/&quot;&gt;IdentityServer in Docker Containers: Handle Logout (Part 4)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to view the final solution, the code for each step and the final code can be found on GitHub &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;solving-the-identityserver-login-form-issue-attempt-7&quot;&gt;Solving The IdentityServer Login Form Issue (Attempt #7)&lt;/h2&gt;
&lt;p&gt;Picking up from where we left off, we’ve successfully set up the containers and established communication between them. Next, we turn to user login issues that can arise if we’re not careful! The login form isn’t working when using &lt;strong&gt;bob/bob&lt;/strong&gt; to authenticate. Why is that? Let’s investigate further.&lt;/p&gt;
&lt;p&gt;For this example, we’ll use the &lt;strong&gt;Chrome browser&lt;/strong&gt;. Press &lt;strong&gt;F12&lt;/strong&gt; to open the &lt;strong&gt;browser developer console&lt;/strong&gt;, then navigate to the &lt;strong&gt;Network&lt;/strong&gt; tab.&lt;/p&gt;
&lt;p&gt;Try logging in as &lt;strong&gt;bob&lt;/strong&gt;/&lt;strong&gt;bob&lt;/strong&gt; again, and look for the request made to the &lt;strong&gt;login&lt;/strong&gt; URL. Once located, click on the &lt;strong&gt;Cookies&lt;/strong&gt; tab.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How to view the cookies for a given request in Chrome browser&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1076&quot; height=&quot;102&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-network-tab-cookies-login-request.WuP-nChE_Z2jfthI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Under the &lt;strong&gt;Cookies&lt;/strong&gt; tab, if you enable the option &lt;strong&gt;Show filtered out request cookies&lt;/strong&gt;, you’ll notice several cookies highlighted in yellow. These cookies are rejected or not included in the request. Hover over the info icon next to each cookie to find out why.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Showing all the blocked and rejected cookies, because we are using HTTP, not HTTPS&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;949&quot; height=&quot;437&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-cookies-tab-samesite-none-secure-filtered.C3ib1UwB_1MQeVR.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The core issue is that we’re using HTTP. Many crucial cookies are blocked due to the &lt;strong&gt;SameSite&lt;/strong&gt; and &lt;strong&gt;Secure&lt;/strong&gt; attributes. These cookies are essential for ASP.NET Core’s &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery&quot;&gt;CSRF protection&lt;/a&gt;, IdentityServer, and the OpenID Connect authentication handler. There is no way around this!&lt;/p&gt;
&lt;p&gt;To resolve this, we must use &lt;strong&gt;HTTPS&lt;/strong&gt; for browser-based interactions. The back-channel communication, however, can continue to use HTTP since no cookies are involved in those requests.&lt;/p&gt;
&lt;p&gt;For more details about troubleshooting cookie problems, check out my blog post: &lt;a href=&quot;//net/debugging-cookie-problems/&quot;&gt;&lt;strong&gt;Debugging Cookie Problems in ASP.NET Core&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Why did we start with HTTP? Beginning with HTTP simplifies the initial setup of the containers and ensures that we can quickly establish communication between them. Once everything works correctly with HTTP, it’s easier to transition to the more secure HTTPS configuration.&lt;/p&gt;
&lt;p&gt;So, how do we implement this? We’ll address that in the next attempt.&lt;/p&gt;
&lt;h2 id=&quot;adding-https-to-identityserver-and-client-attempt-8&quot;&gt;Adding HTTPS to IdentityServer and Client (Attempt #8)&lt;/h2&gt;
&lt;p&gt;To resolve the cookie issue, we need our application’s client and IdentityService to support &lt;strong&gt;HTTPS&lt;/strong&gt; while keeping &lt;strong&gt;HTTP&lt;/strong&gt; for back-channel communication. This requires exposing port &lt;strong&gt;443&lt;/strong&gt; and adding separate port mappings, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;adding and exposing the HTTPS port 443 to both containers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;650&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-identityserver-http-port-mapping.Bi4kuHtW_ZKwwqs.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We begin by updating the two &lt;strong&gt;Dockerfiles&lt;/strong&gt; to expose port &lt;strong&gt;443&lt;/strong&gt; and configuring ASP.NET Core to listen on ports &lt;strong&gt;80&lt;/strong&gt; and &lt;strong&gt;443&lt;/strong&gt;. This is done by adding or updating the following lines in each file:&lt;/p&gt;
&lt;p&gt;EXPOSE 80 443&lt;/p&gt;
&lt;p&gt;ENV ASPNETCORE_URLS=http://+:80;https://+:443&lt;/p&gt;
&lt;p&gt;Next, we modify the &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file and map the two HTTPS ports:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5001:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  identity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7001:443&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we update the &lt;strong&gt;AddOpenIDConnect&lt;/strong&gt; configuration in the client to use &lt;strong&gt;HTTPS&lt;/strong&gt; for all browser-related interactions:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://localhost:7001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.MetadataAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://identity:80/.well-known/openid-configuration&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.OnRedirectToIdentityProvider &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        context.ProtocolMessage.IssuerAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://localhost:7001/connect/authorize&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we launch the Docker Compose setup now, it crashes with the following error:&lt;/p&gt;
&lt;p&gt;System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.&lt;/p&gt;
&lt;p&gt;If this exception doesn’t appear in the logs, consider adjusting the log levels in &lt;strong&gt;appsettings.json&lt;/strong&gt; for more detailed error messages.&lt;/p&gt;
&lt;h3 id=&quot;why-does-this-error-occur&quot;&gt;Why does this error occur?&lt;/h3&gt;
&lt;p&gt;This error happens because the application cannot find a valid HTTPS certificate. ASP.NET Core expects a trusted certificate to configure HTTPS endpoints, but it isn’t available. We will resolve this issue by setting up the required certificate in the next attempt.&lt;/p&gt;
&lt;h2 id=&quot;adding-the-certificate-to-identityserver--client-attempt-9&quot;&gt;Adding the certificate to IdentityServer / Client (Attempt #9)&lt;/h2&gt;
&lt;p&gt;To enable HTTPS, we need to add a &lt;strong&gt;TLS certificate&lt;/strong&gt; to each container. But where do we get such a certificate? We could create a self-signed certificate or use a paid trusted certificate.&lt;/p&gt;
&lt;p&gt;However, since this setup is only for local development and we want to keep using the localhost domain, the simplest solution is to use the existing developer certificate that we installed and trusted when setting up ASP.NET Core and Visual Studio.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The port mapping between the containers and the host machine.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;927&quot; height=&quot;629&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-identityservice-port-mapping-diagra.CSnLYBDS_Z5a9RV.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Run the following command to check if you have a valid development certificate installed:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bat&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dotnet dev-certs httpsA valid HTTPS certificate is already present.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more information, refer to the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs&quot;&gt;&lt;strong&gt;official dotnet dev-certs documentation&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We then run the following command to export the certificate from the Windows certificate store to a separate file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bat&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; dotnet dev-certs https --export-path ./aspnetcore-dev-cert.pfx --password MyPw123 --format PFX&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will create a file named &lt;strong&gt;aspnetcore-dev-cert.pfx&lt;/strong&gt; at the root of your project directory. In a real-world scenario, storing this file outside the repository is best to avoid accidental commits. As a best practice, ensure that &lt;strong&gt;.pfx&lt;/strong&gt; files are included in your &lt;strong&gt;.gitignore&lt;/strong&gt; file.&lt;/p&gt;
&lt;p&gt;Next, we need to import the certificate into both containers. This is done by mounting the certificate file as a volume in each container. Modify the &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5001:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./aspnetcore-dev-cert.pfx:/app/aspnetcore-dev-cert.pfx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  identity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7001:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./aspnetcore-dev-cert.pfx:/app/aspnetcore-dev-cert.pfx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more details on setting up HTTPS with Docker Compose, check out the guide on &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/docker-compose-https&quot;&gt;&lt;strong&gt;Hosting ASP.NET Core images with Docker Compose over HTTPS&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Security Warning&lt;/strong&gt;&lt;br&gt;
This approach works and is used here for simplicity and demonstration purposes. For a more secure setup, store the certificate outside the project folder or inject it from a trusted key vault at runtime.&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Tore Nestenius&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, we need to configure ASP.NET Core to use this certificate by setting its path and password as environment variables in the &lt;strong&gt;compose.yml&lt;/strong&gt; file:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5001:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    depends_on&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./aspnetcore-dev-cert.pfx:/app/aspnetcore-dev-cert.pfx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ASPNETCORE_Kestrel__Certificates__Default__Path=/app/aspnetcore-dev-cert.pfx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ASPNETCORE_Kestrel__Certificates__Default__Password=MyPw123&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  identity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7001:443&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;./aspnetcore-dev-cert.pfx:/app/aspnetcore-dev-cert.pfx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    environment&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ASPNETCORE_Kestrel__Certificates__Default__Path=/app/aspnetcore-dev-cert.pfx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ASPNETCORE_Kestrel__Certificates__Default__Password=MyPw123&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these steps completed, we have successfully mounted the certificate into each container and configured ASP.NET Core to use it. This allows us to access the services over HTTPS using the following URLs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://localhost:5001/&lt;/code&gt; for the client&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://localhost:7001/&lt;/code&gt; for the identity service&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Awesome! 🥳&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
&lt;p&gt;We’ve resolved the login issues using HTTPS and secured the communication between the host and containers. In the &lt;a href=&quot;//net/identityserver-in-docker-containers-part-4/&quot;&gt;final post&lt;/a&gt;, we’ll address the sign-out challenges and wrap up the key lessons from this series. Stay tuned!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-4/&quot;&gt;&lt;strong&gt;To the next post &gt;&gt;&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery&quot;&gt;Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite&quot;&gt;SameSite cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/samesite-cookie-recipes/&quot;&gt;SameSite cookie recipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromium.org/updates/same-site/&quot;&gt;SameSite Updates history&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;OpenID Connect for Developers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-identityserver-and-openid-connect/&quot;&gt;Introduction to IdentityServer and OpenID-Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>identityserver</category><category>openid-connect</category></item><item><title>IdentityServer in Docker Containers: Networking (Part 2)</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-2/</link><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-2/</guid><description>This is part 2 of a blog series on containerizing a Duende IdentityServer and a client application. In this post, we resolve communication challenges that</description><pubDate>Mon, 25 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is &lt;strong&gt;part 2&lt;/strong&gt; of a blog series on containerizing a Duende IdentityServer and a client application. In this post, we resolve communication challenges that arise when these applications run in separate Docker containers. You’ll learn how to fix back-channel issues, handle localhost conflicts, and establish proper networking between the client and IdentityServer.&lt;/p&gt;
&lt;p&gt;This blog has been broken up into four separate posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker Containers: Adding Containers (Part 1)&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IdentityServer in Docker Containers: Networking (Part 2)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-3/&quot;&gt;IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-4/&quot;&gt;IdentityServer in Docker Containers: Handle Logout (Part 4)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to view the final solution, the code for each step and the final code can be found on GitHub &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/IdentityServer-in-Docker&quot;&gt;&lt;strong&gt;here.&lt;/strong&gt;&lt;/a&gt; &lt;/p&gt;
&lt;h2 id=&quot;fixing-the-identityserver-back-channel-attempt-2&quot;&gt;Fixing the IdentityServer Back-Channel (Attempt #2)&lt;/h2&gt;
&lt;p&gt;In the previous post, we learned we can’t use localhost from within the containers, so what should we do instead?&lt;/p&gt;
&lt;p&gt;Docker Compose automatically creates a default network for all services defined in the &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file. This network allows containers to reach each other using their service names as DNS names.&lt;/p&gt;
&lt;p&gt;In our setup, the client container should communicate with the identity container using &lt;strong&gt;&lt;a href=&quot;http://identity:7000&quot;&gt;http://identity:7000&lt;/a&gt;&lt;/strong&gt; rather than &lt;code&gt;http://localhost:7000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How we want the Client app to communicate with IdentityServer over the Docker container network.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1376&quot; height=&quot;868&quot; src=&quot;https://nestenius.se/_astro/docker-compose-container-networking-client-identity-service-.bNs-JTUZ_5q8Up.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {     options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://identity:7000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;     &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;. };&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, identity is the name of the &lt;strong&gt;IdentityService&lt;/strong&gt; container as defined in our Docker Compose file.&lt;/p&gt;
&lt;p&gt;But when we run this, we’ll still encounter an error. Why?&lt;/p&gt;
&lt;p&gt;The issue lies in how Docker Compose handles container communication. Services within Docker Compose communicate using their &lt;strong&gt;service&lt;/strong&gt; &lt;strong&gt;names&lt;/strong&gt; and the &lt;strong&gt;&lt;em&gt;internal&lt;/em&gt;&lt;/strong&gt; container ports they expose, not the &lt;strong&gt;external&lt;/strong&gt; ports.&lt;/p&gt;
&lt;p&gt;This means that while external clients (such as a web browser or Postman) outside the Docker network should use port &lt;strong&gt;7000&lt;/strong&gt; to reach the IdentityService, containers within Docker should use the &lt;strong&gt;&lt;em&gt;internal&lt;/em&gt;&lt;/strong&gt; port, which is &lt;strong&gt;80&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To resolve this, we need to adjust the port in the code to use port &lt;strong&gt;80&lt;/strong&gt; instead, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://identity:7000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration change ensures that the client container can successfully communicate with the identity container using the correct internal port.&lt;/p&gt;
&lt;p&gt;However, this still needs to be fixed!&lt;/p&gt;
&lt;p&gt;We end up with the browser trying to reach &lt;strong&gt;&lt;a href=&quot;http://identity/connect/authorize&quot;&gt;http://identity/connect/authorize&lt;/a&gt;&lt;/strong&gt;, but obviously, we can’t use this outside Docker. &lt;strong&gt;Identity&lt;/strong&gt; is only a name that can be used within the Docker network.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Problem! We end up with the browser trying to reach http://identity/connect/authorize&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;589&quot; height=&quot;96&quot; src=&quot;https://nestenius.se/_astro/browser-address-bar-identity-container-authorize-endpoint.BfmgZa2y_nIICI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We could resolve this by adding the Identity hostname to the operating system’s &lt;strong&gt;host&lt;/strong&gt; file. However, I prefer a solution that doesn’t require modifying this file.&lt;/p&gt;
&lt;h3 id=&quot;the-issue-with-setting-the-authority-in-identityserver&quot;&gt;The Issue with Setting the Authority in IdentityServer&lt;/h3&gt;
&lt;p&gt;Setting the &lt;strong&gt;authority&lt;/strong&gt; in the client to &lt;strong&gt;&lt;a href=&quot;http://identity:80&quot;&gt;http://identity:80&lt;/a&gt;&lt;/strong&gt; might seem like the obvious solution, but it introduces a few complications!&lt;/p&gt;
&lt;p&gt;In our current setup, the &lt;strong&gt;Authority&lt;/strong&gt; configuration serves three distinct purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Retrieving Configuration Documents:&lt;/strong&gt; It is used by the OpenID Connect authentication middleware in the client container to retrieve the &lt;strong&gt;/.well-known/openid-configuration&lt;/strong&gt; and &lt;strong&gt;/.well-known/openid-configuration/jwks&lt;/strong&gt; documents from IdentityServer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenID Connect Flows:&lt;/strong&gt; The authority URL is crucial during various OpenID Connect flows, including authentication, token exchange, and sign-out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Browser Redirection:&lt;/strong&gt; It also tells the browser where to redirect the user when authentication is required so that the user can authenticate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The problem arises because the client container needs to use &lt;strong&gt;&lt;a href=&quot;http://identity:80&quot;&gt;http://identity:80&lt;/a&gt;&lt;/strong&gt; to reach IdentityServer from within the Docker network. In contrast, the browser (which is outside the Docker network) needs to use &lt;code&gt;http://localhost:7000&lt;/code&gt; to reach IdentityServer.&lt;/p&gt;
&lt;p&gt;This mismatch between the internal and external URLs complicates the situation.&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-metadataaddress-attempt-3&quot;&gt;Introducing the MetaDataAddress (Attempt #3)&lt;/h2&gt;
&lt;p&gt;To try to address this, we can introduce the &lt;strong&gt;MetadataAddress&lt;/strong&gt; property in our configuration, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://localhost:7000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.MetadataAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://identity:80&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By setting these two properties, we try to achieve the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Authority&lt;/strong&gt;: This is set to the externally accessible URL of IdentityServer (&lt;code&gt;http://localhost:7000&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MetadataAddress&lt;/strong&gt;: This points to where the OpenID Connect authentication handler can reach IdentityServer internally (&lt;a href=&quot;http://identity:80&quot;&gt;http://identity:80&lt;/a&gt;). This allows the client container to correctly obtain the needed configuration documents from IdentityServer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this configuration, we maintain compatibility for both internal container-to-container communication and external browser redirects.&lt;/p&gt;
&lt;p&gt;The application still does not work, and so we get a new exception in the browser:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;JsonException: IDX10805: Error deserializing json: &apos;&amp;#x3C;!DOCTYPE html&gt; &amp;#x3C;html lang=&quot;en&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do we solve this?&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Tore Nestenius&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-identityserver-metadataaddress-attempt-4&quot;&gt;Fixing the IdentityServer MetadataAddress (Attempt #4)&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;MetadataAddress&lt;/strong&gt; property in OpenID Connect should be set to the &lt;strong&gt;absolute&lt;/strong&gt; &lt;strong&gt;URL&lt;/strong&gt; of the metadata document, not just the base URL. This is an essential distinction because MetadataAddress specifies the exact endpoint from which the OpenID Connect middleware will retrieve the configuration.&lt;/p&gt;
&lt;p&gt;The solution is to change the configuration to:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://localhost:7000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.MetadataAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://identity:80/.well-known/openid-configuration&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, retrieving the metadata from IdentityServer should work. However, we are back to the problem where the browser tries to reach &lt;strong&gt;&lt;a href=&quot;http://identity/connect/authorize&quot;&gt;http://identity/connect/authorize&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;observing-the-back-channel-communication&quot;&gt;Observing the Back-Channel Communication&lt;/h3&gt;
&lt;p&gt;The client’s OpenID Connect authentication handler occasionally makes HTTP requests to the authorization server (IdentityServer), which is known as the &lt;strong&gt;back channel&lt;/strong&gt;. By default, this communication could be more visible from the outside. However, implementing a custom &lt;strong&gt;backchannel handler&lt;/strong&gt; to log these interactions can make it more transparent.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;We can implement custom backchannel handler to log these interactions can make it more transparent.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;216&quot; src=&quot;https://nestenius.se/_astro/openidconnect-handler-backchannel-httpclient-to-identityserv.k4MxPIOE_1RW7JK.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To implement this, we create a new delegating handler named &lt;strong&gt;BackChannelListener&lt;/strong&gt;, implemented as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BackChannelListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DelegatingHandler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BackChannelListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() : &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HttpClientHandler&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    protected&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; override&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HttpResponseMessage&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SendAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HttpRequestMessage&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; request&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                                                 CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; token&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Stopwatch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        sw.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Start&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; base&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SendAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(request, token);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        sw.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Stop&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Log.Logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SourceContext&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BackChannelListener&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                   .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;### BackChannel request to &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;RequestUri&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;AbsoluteUri&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; took &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sw&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ElapsedMilliseconds&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;()}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; ms&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This implementation will measure the time it takes for each request to complete and log it using Serilog. This will give us insights into the back-channel communication between the client and IdentityServer.&lt;/p&gt;
&lt;p&gt;Next, we configure the OpenID Connect handler to use this handler:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.BackchannelHttpHandler &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BackChannelListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.BackchannelTimeout &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; TimeSpan.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromSeconds&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this setup, we should be able to see in the logs each time the client makes a request to IdentityServer over the back-channel, like the examples below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### BackChannel request to http://identity/.well-known/openid-configuration took 362 ms&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;### BackChannel request to http://identity/.well-known/openid-configuration/jwks took 30 ms&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These logs provide valuable visibility into internal HTTP communication, helping us better understand the performance and behavior of back-channel requests.&lt;/p&gt;
&lt;p&gt;However, our application still can’t authenticate; let’s address that in the next attempt.&lt;/p&gt;
&lt;h2 id=&quot;understanding-the-discovery-document-issue-attempt-5&quot;&gt;Understanding the Discovery Document Issue (Attempt #5)&lt;/h2&gt;
&lt;p&gt;If we run the application now, we still can’t authenticate successfully because the browser tries to access&lt;br&gt;
&lt;strong&gt;&lt;a href=&quot;http://identity/connect/authorize&quot;&gt;http://identity/connect/authorize&lt;/a&gt;&lt;/strong&gt;… instead of the expected URL.&lt;/p&gt;
&lt;p&gt;To understand what’s happening, we can add a minor tweak to our &lt;strong&gt;BackChannelListener&lt;/strong&gt; to log the back-channel responses from IdentityServer to the console. This will give us insight into what the client receives.&lt;/p&gt;
&lt;p&gt;We add this code to the handler:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// HACK: log the response body; never run this in production&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.Logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SourceContext&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BackChannelListener&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;#####################################&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.Logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SourceContext&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BackChannelListener&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(responseContent);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.Logger.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ForContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;SourceContext&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BackChannelListener&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;#####################################&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;⚠️Important⚠️&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The back-channel request and response contain sensitive information, such as tokens or configuration data. Therefore, you should only log this information during troubleshooting and never in a production environment.&lt;/p&gt;
&lt;p&gt;With this in place, we can see that the client correctly retrieves the discovery document as expected when we attempt to authenticate. The log shows the following output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;issuer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;jwks_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity/.well-known/openid-configuration/jwks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://identity/connect/authorize&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, when we navigate to the discovery document directly from our browser at&lt;br&gt;
&lt;code&gt;http://localhost:7000/.well-known/openid-configuration&lt;/code&gt;, we get a different result:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;issuer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://localhost:7000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;jwks_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://localhost:7000/.well-known/openid-configuration/jwks&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://localhost:7000/connect/authorize&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issuer URL changes depending on where the request is made:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inside Docker: &lt;strong&gt;&lt;a href=&quot;http://identity&quot;&gt;http://identity&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Outside Docker: &lt;code&gt;http://localhost:7000&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This discrepancy occurs because the identity provider’s issuer value is dynamically generated based on the origin of the incoming request URL.&lt;/p&gt;
&lt;h2 id=&quot;solving-the-initial-authentication-problem-attempt-6&quot;&gt;Solving The Initial Authentication Problem (Attempt #6)&lt;/h2&gt;
&lt;p&gt;The main issue is that when we click the log-in button, the application receives information from IdentityServer indicating that the authorization endpoint is &lt;strong&gt;&lt;a href=&quot;http://identity/connect/authorize&quot;&gt;http://identity/connect/authorize&lt;/a&gt;&lt;/strong&gt;. Naturally, it tries to redirect the browser to that URL for user authentication using the authorization code flow.&lt;/p&gt;
&lt;p&gt;To address this in a development environment, we can add the following event handler to intercept the redirect and adjust the URL to the correct one:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.OnRedirectToIdentityProvider &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        context.ProtocolMessage.IssuerAddress &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;http://localhost:7000/connect/authorize&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on your deployment setup, you may need to implement a different strategy in production. The key takeaway here is to understand the root cause of the issue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important!⚠️&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let me know if you have a better approach to solving this problem!&lt;/p&gt;
&lt;p&gt;With this change in place, the application should now correctly redirect to IdentityServer and display the login screen as expected.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;IdentityServer login page&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;603&quot; height=&quot;516&quot; src=&quot;https://nestenius.se/_astro/duende-identityserver-local-account-login-page.1-TiDi7t_1V2RCq.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;great-are-we-done&quot;&gt;&lt;strong&gt;Great! Are we done?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Not quite yet! When we try to log in with the default &lt;strong&gt;bob/bob&lt;/strong&gt; user, the form doesn’t work. ☹&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
&lt;p&gt;In the next post, we’ll address the login form issue and introduce HTTPS to secure communication between the host and the containers, enabling successful and secure authentications.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-3/&quot;&gt;&lt;strong&gt;To the next post &gt;&gt;&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-identityserver-and-openid-connect/&quot;&gt;Introduction to IdentityServer and OpenID-Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>identityserver</category><category>openid-connect</category></item><item><title>IdentityServer in Docker Containers - Part 1</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-1/</link><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-1/</guid><description>Getting Duende IdentityServer and a client application up and running in separate containers can be challenging. This blog post will provide a</description><pubDate>Fri, 22 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Getting &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt; and a client application up and running in separate containers can be challenging. This blog post will provide a step-by-step guide for a smooth setup and show you how to resolve common challenges along the way. We will also learn about security, cookies, ports, containers, and certificates.&lt;/p&gt;
&lt;p&gt;This is a big topic, so this blog is divided into four posts. You can get to where you need to be below, but for handy context, it may be helpful to read this post first.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⦁ IdentityServer in Docker Containers: Adding Containers (Part 1)&lt;/strong&gt;&lt;br&gt;
⦁ &lt;a href=&quot;//net/identityserver-in-docker-containers-part-2/&quot;&gt;IdentityServer in Docker Containers: Networking (Part 2)&lt;/a&gt;&lt;br&gt;
⦁ &lt;a href=&quot;//net/identityserver-in-docker-containers-part-3/&quot;&gt;IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)&lt;/a&gt;&lt;br&gt;
⦁ &lt;a href=&quot;//net/identityserver-in-docker-containers-part-4/&quot;&gt;IdentityServer in Docker Containers: Handle Logout (Part 4)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to view the final solution, the code for each step and the final code can be found on GitHub &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;background---containerizing-a-duende-identityserver&quot;&gt;Background - containerizing a Duende IdentityServer&lt;/h2&gt;
&lt;p&gt;I spend quite a bit of my time helping developers on &lt;a href=&quot;https://stackoverflow.com/users/68490/tore-nestenius&quot;&gt;Stack Overflow&lt;/a&gt;, and I’ve noticed that many struggle with containerizing a Duende IdentityServer solution. There are numerous pitfalls, gotchas, and misconceptions that we need to understand and address.&lt;/p&gt;
&lt;p&gt;The series focuses on deploying &lt;strong&gt;Duende IdentityServer&lt;/strong&gt; locally using &lt;strong&gt;Docker Compose&lt;/strong&gt;, emphasizing the fundamentals rather than a production-ready implementation.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The two Docker Containers (Client Application and IdentityService) we will use in this blog post.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;473&quot; height=&quot;548&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-app-and-identityserver.ujR9tiqh_Z2hJR8r.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This series takes a step-by-step approach to tackle the challenge, highlighting common mistakes and pitfalls along the way. Rather than simply presenting the final solution, it encourages a shared learning experience, and troubleshooting issues together to build a strong understanding of the fundamentals for containerizing IdentityServer with Docker Compose.&lt;/p&gt;
&lt;h2 id=&quot;important--not-for-production&quot;&gt;Important – Not for Production&lt;/h2&gt;
&lt;p&gt;The focus is on getting the application to work locally in a containerized environment using &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose&lt;/a&gt;. Making this setup production-ready involves additional considerations beyond the scope of this post.&lt;/p&gt;
&lt;h2 id=&quot;the-setup--identityserver-and-the-client-application&quot;&gt;The Setup – IdentityServer and the Client Application&lt;/h2&gt;
&lt;p&gt;We have two ASP.NET Core 8 applications: one client application and one IdentityServer application. Both applications have been tweaked to make them more educational.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The relationship between the IdentityServer, Client Application and the Browser in this setup.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;736&quot; height=&quot;494&quot; src=&quot;https://nestenius.se/_astro/browser-identityserver-client-app-architecture-diagram.C6XDr46Z_Z1W8DoG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;We name the &lt;strong&gt;IdentityServer&lt;/strong&gt; project &lt;strong&gt;IdentityService&lt;/strong&gt; to avoid potential naming collisions in our code.&lt;/p&gt;
&lt;p&gt;The complete source code for this setup and each attempt to create it can be found in my public &lt;strong&gt;&lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;GitHub repository&lt;/a&gt;&lt;/strong&gt;. The source code in the Start project folder contains the start code, which you can run from within Visual Studio and authenticate using the credentials &lt;strong&gt;bob/bob&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-goal--containerized-identityserver-solution&quot;&gt;The Goal – containerized IdentityServer solution&lt;/h2&gt;
&lt;p&gt;This setup aims to package two applications into separate containers and run them locally using &lt;strong&gt;Docker Desktop&lt;/strong&gt; with &lt;strong&gt;Docker Compose&lt;/strong&gt;. Authentication against IdentityServer should still work seamlessly after containerization.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the browser communicates with the Docker Container host.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;768&quot; height=&quot;536&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-app-and-identityserver-architecture.BAEqcAIZ_ZmisI4.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;containerize-identityserver-and-the-client-attempt-1&quot;&gt;Containerize IdentityServer and The Client (Attempt #1)&lt;/h2&gt;
&lt;p&gt;In our first attempt, we’ll create the following setup using HTTP. Starting with HTTP is beneficial for its simplicity and highlighting some gotchas we’ll explore later in this blog post series. We’ll introduce and use HTTPS later.&lt;/p&gt;
&lt;p&gt;We add two Dockerfiles (&lt;strong&gt;Dockerfile_Client&lt;/strong&gt; and &lt;strong&gt;Dockerfile_Identity&lt;/strong&gt;) to containerize the client and IdentityServer projects. The code for them can be found on &lt;strong&gt;&lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/IdentityServer-in-Docker/Attempt%20%231&quot;&gt;GitHub&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Next, we add a Docker compose file to launch the two containers and expose ports 5000 and 7000 to the host machine.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The port mapping between the host and the container ports.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;602&quot; height=&quot;505&quot; src=&quot;https://nestenius.se/_astro/docker-containers-port-mapping-client-identityserver.Bg12_fQL_Z1W1BeI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  client&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;5000:80&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;  identity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      context&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;      dockerfile&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Dockerfile_Identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;    ports&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;7000:80&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the client application (program class), we modify the OpenID Connect authority as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;http://localhost:7000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To build and deploy the containers, we use the following Docker Compose command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;docker-compose&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; up&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --build&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A simple batch file (&lt;strong&gt;RunCompose.bat&lt;/strong&gt;) has been added that runs the command for us.&lt;br&gt;
When we run the containers, we can observe that both are up and running and can be accessed using &lt;code&gt;http://localhost:5000/&lt;/code&gt; and &lt;code&gt;http://localhost:7000/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, when we click on the login button, we get the following error:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;IOException: IDX20804: Unable to retrieve document from: &apos;http://localhost:7000/.well-known/openid-configuration&apos;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This error occurs because the client application tries to download the IdentityServer’s discovery document and public signing keys from the specified URL but fails to do so. The discovery document is crucial as it contains endpoints and configuration data that the client needs to communicate with IdentityServer securely.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Client container trying to connect with the IdentityServer container.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;768&quot; height=&quot;395&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-identityserver-localhost-port-confl.BdtIEAT9_Z1necTS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The URL works fine when I go to &lt;code&gt;http://localhost:7000/.well-known/openid-configuration&lt;/code&gt; in my browser!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Accessing the OpenID Connect Discovery document in the browser. Located at the well-known endpoint.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;516&quot; height=&quot;186&quot; src=&quot;https://nestenius.se/_astro/duende-identityserver-openid-configuration-localhost-7000.DutiAV42_Z1LjwEh.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, why does this not work when it works from the browser?&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Tore Nestenius&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-localhost-and-containers&quot;&gt;The problem with Localhost and Containers&lt;/h2&gt;
&lt;p&gt;In our setup, the client attempts to make a cross-container HTTP request like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Client container successfully connecting with the Duende IdentityServer.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;768&quot; height=&quot;399&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-app-identityservice-port-mapping.fv4CyLdX_Z1K5dX3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, when requesting &lt;code&gt;http://localhost:7000/&lt;/code&gt;, it is crucial to understand that this URL is resolved within the client container itself, not to the &lt;strong&gt;IdentityService&lt;/strong&gt; that we intended to reach.&lt;/p&gt;
&lt;p&gt;The image below shows what happens in our current setup. Obviously, nothing is listening on port &lt;strong&gt;7000&lt;/strong&gt; inside the client container.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Showing why trying make a HTTP request to another container using localhost fails.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;768&quot; height=&quot;397&quot; src=&quot;https://nestenius.se/_astro/docker-containers-client-app-identityservice-port-mapping-2.Dm1fKJap_xCNea.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The issue arises from having multiple isolated &lt;strong&gt;localhost&lt;/strong&gt; instances in the setup, each tied to a specific container:&lt;/p&gt;
&lt;p&gt;⦁ Client container has its own localhost.&lt;br&gt;
⦁ Identity container has its own localhost.&lt;br&gt;
⦁ Host machine has yet another localhost.&lt;/p&gt;
&lt;p&gt;As a result, when the client container tries to access &lt;code&gt;http://localhost:7000/&lt;/code&gt;, the request is confined to the client container itself and never reaches the IdentityServer container.&lt;/p&gt;
&lt;p&gt;The image below illustrates how these three separate localhost environments exist in our setup:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The separate localhosts in our setup (Machine host and one in each container)&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;768&quot; height=&quot;620&quot; src=&quot;https://nestenius.se/_astro/docker-host-machine-client-identityserver-port-mapping-diagr.DJKJg-W0_m9rH8.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The key here is &lt;strong&gt;Docker’s network isolation&lt;/strong&gt;, which prevents containers from communicating with one another using localhost.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;
&lt;p&gt;We’ll resolve this further in my &lt;a href=&quot;//net/identityserver-in-docker-containers-part-2/&quot;&gt;next post&lt;/a&gt;, were we  will continue refining the communication between the two containers as we work toward building a fully functional, containerized IdentityServer solution.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-2/&quot;&gt;&lt;strong&gt;To the next post &gt;&gt;&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-identityserver-and-openid-connect/&quot;&gt;Introduction to IdentityServer and OpenID-Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>identityserver</category><category>openid-connect</category></item><item><title>Pushed Authorization Requests (PAR) in ASP.NET Core 9</title><link>https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/</link><guid isPermaLink="true">https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/</guid><description>ASP.NET Core 9 introduces support for Pushed Authorization Requests (PAR) in its OpenIdConnect authentication handler. But what exactly is PAR, and why</description><pubDate>Mon, 04 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ASP.NET Core 9 introduces support for &lt;strong&gt;P&lt;/strong&gt;ushed &lt;strong&gt;A&lt;/strong&gt;uthorization &lt;strong&gt;R&lt;/strong&gt;equests (&lt;strong&gt;PAR&lt;/strong&gt;) in its OpenIdConnect authentication handler. But what exactly is PAR, and why does it matter? In this post, I’ll explain what PAR is, how it works, how to use it with &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;&lt;strong&gt;Duende IdentityServer&lt;/strong&gt;&lt;/a&gt;, and when you should consider using it in your applications.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How does Pushed Authorization Requests (PAR) fit into my OIDC architecture?&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;998&quot; height=&quot;705&quot; src=&quot;https://nestenius.se/_astro/par-oauth-browser-identity-server-client-application-diagram.BUIB-_8g_1HH96m.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-pushed-authorization-requests-par&quot;&gt;What is Pushed Authorization Requests (PAR)?&lt;/h2&gt;
&lt;p&gt;PAR is a relatively new OAuth standard that is designed to enhance the security of OAuth and OpenID Connect (OIDC) flows. Traditionally, authorization parameters (such as client credentials, scopes, and redirect URIs) are passed through the browser (front channel) via URL query strings. This approach exposes sensitive data to potential attackers and increases the risk of tampering. PAR addresses this by shifting the transmission of authorization parameters from the front channel to the back channel through direct machine-to-machine HTTP calls.&lt;/p&gt;
&lt;p&gt;This change brings several important benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improved Security&lt;/strong&gt;: Since authorization parameters are sent via a secure back-channel request, they are no longer exposed in browser URLs, reducing the risk of sensitive information (like Personally Identifiable Information, or PII) being leaked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tamper Resistance&lt;/strong&gt;: By pushing authorization requests through the back end, attackers are prevented from modifying the parameters, such as the requested scopes or access levels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shorter URLs&lt;/strong&gt;: In complex OAuth/OIDC scenarios (such as when using &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9396.html&quot;&gt;Rich Authorization Requests&lt;/a&gt;, URLs can become excessively long, which can cause issues with some browsers and networking systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-are-pushed-authorization-requests-important&quot;&gt;Why are Pushed Authorization Requests Important?&lt;/h2&gt;
&lt;p&gt;PAR has gained a lot of traction in industries with stringent security requirements, such as &lt;strong&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Open_banking&quot;&gt;open banking&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;healthcare&lt;/strong&gt;. The &lt;strong&gt;&lt;a href=&quot;https://openid.net/specs/fapi-2_0-security-02.html&quot;&gt;FAPI 2.0 Security Profile&lt;/a&gt;&lt;/strong&gt;, which is developed by the Financial-grade API (FAPI) working group within the OpenID Foundation, now mandates the use of PAR for increased security.&lt;/p&gt;
&lt;p&gt;As a result, many identity providers, including &lt;strong&gt;&lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&quot;https://curity.io/&quot;&gt;Curity&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href=&quot;https://www.keycloak.org/&quot;&gt;Keycloak&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href=&quot;https://www.authlete.com/&quot;&gt;Authlete&lt;/a&gt;&lt;/strong&gt;, now support PAR.&lt;/p&gt;
&lt;h2 id=&quot;authenticating-without-pushed-authorization-requests-par&quot;&gt;Authenticating without Pushed Authorization Requests (PAR)&lt;/h2&gt;
&lt;p&gt;When you’re using the &lt;a href=&quot;https://tn-data.se/openid-connect/#authorization-code-flow&quot;&gt;&lt;strong&gt;Authorization Code flow&lt;/strong&gt;&lt;/a&gt; to authenticate with an &lt;strong&gt;authorization server&lt;/strong&gt; without using PAR, the process typically follows this sequence:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Authenticating without Pushed Authorization Requests (PAR) - Example flow&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;628&quot; src=&quot;https://nestenius.se/_astro/oidc-authorization-code-flow-browser-client-identity-server.BPWatnb-_Z1jFfP3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;initial-request-to-the-authorization-endpoint&quot;&gt;Initial Request to the Authorization Endpoint&lt;/h3&gt;
&lt;p&gt;When a user needs to authenticate, the OpenID Connect handler in the client initiates the authentication flow by redirecting the user to the authorization endpoint. This request includes all of the necessary authorization parameters, which are sent through the browser’s front channel via the URL, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Initial Request to the Authorization Endpoint on IdentityServer , without using PAR&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;858&quot; src=&quot;https://nestenius.se/_astro/oidc-traditional-auth-params-query-string-flow.D6FcXY61_bLcHa.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s a sample request:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://myidentityserver.com/connect/authorize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;myclient&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid%20profile%20email%20offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mmTnrL97RIKECAhsDKXP0lXubokXxr5tPqq_fOP2rtg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge_method=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_mode=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;form_post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638645055062345714....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-SKU=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ID_NET9_0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-ver=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;8.1.2.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HTTP/1.1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See my blog post &lt;strong&gt;Demystifying &lt;a href=&quot;https://nestenius.se/2023/12/13/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/strong&gt;  for more details about what the &lt;strong&gt;state&lt;/strong&gt; and &lt;strong&gt;nonce&lt;/strong&gt; contains.&lt;/p&gt;
&lt;h3 id=&quot;receiving-the-authorization-code&quot;&gt;Receiving the Authorization Code&lt;/h3&gt;
&lt;p&gt;After the user authenticates and consents to the request, the authorization server sends the application an authorization code.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the client receives the the Authorization Code from IdentityServer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;742&quot; height=&quot;582&quot; src=&quot;https://nestenius.se/_astro/oidc-authorization-code-flow-browser-identity-server-client-.D281OqZZ_WP1QT.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This code is delivered to the &lt;strong&gt;redirect_uri&lt;/strong&gt; (in this example, &lt;code&gt;https://localhost:5001/signin-oidc&lt;/code&gt;) through a POST request:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://localhost:5001/signin-oidc &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/x-www-form-urlencoded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code=7C6DA4BE86ED80FD9DB9A077320C2DF0FC2B1CD43D742C5CFDD466FF5B850188-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid+profile+email+offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd3_JvtZYeDos8gQO0om...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;session_state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;vXoggS4g5yYhV8pQNnZ5eq5DLC4UKE2Pq4ggWPNEOX8...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;iss=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Fmyidentityserver.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This request also includes the &lt;strong&gt;state&lt;/strong&gt; parameter to ensure the response matches the original request.&lt;/p&gt;
&lt;h3 id=&quot;exchanging-the-authorization-code-for-tokens&quot;&gt;Exchanging the Authorization Code for Tokens&lt;/h3&gt;
&lt;p&gt;Once the client application receives the authorization code, it can exchange it for tokens by making a request to the token endpoint over the back channel.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the ASP.NET Core client Exchanges the Authorization Code for the Tokens (ID, access and refresh)&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;627&quot; src=&quot;https://nestenius.se/_astro/oidc-back-channel-token-exchange-identity-server-client.CalnyENs_23FvSl.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s an example of that request:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://myidentityserver.com/connect/token &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/x-www-form-urlencoded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=myclient&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_secret=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mysecret&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;7C6DA4BE86ED80FD9DB9A077320C2DF0FC2B1CD43D742C5CFDD466FF5B850188-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;grant_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;authorization_code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_verifier=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ZQVkrVGSqxYPrPyO1Bjppnd-Kv3rH3LX_8U3Q7ScB5I&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the authorization code exchange is successful, the authorization server responds with the requested tokens:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/json; charset=UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;id_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;eyJhbGciOiJSUzI1NiIsImt...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;access_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;eyJhbGciOiJSUzI1NiIsImtpZ...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;expires_in&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;token_type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bearer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;refresh_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;CAB1E2AC3A9904AF307F8B7...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;scope&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;openid profile email offline_access&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This flow follows the typical OAuth &lt;strong&gt;Authorization Code flow&lt;/strong&gt; without using PAR. While this approach works well, sending sensitive parameters via the front channel (browser URL) can expose them to potential tampering or leakage. That’s where PAR comes in; instead, it shifts these parameters to a more secure back-channel communication.&lt;/p&gt;
&lt;h2 id=&quot;pushed-authorization-requests-par-metadata&quot;&gt;Pushed Authorization Requests (PAR) metadata&lt;/h2&gt;
&lt;p&gt;According to &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9126&quot;&gt;RFC 9126&lt;/a&gt;, the discovery document of an identity provider includes new fields related to Pushed Authorization Requests (PAR). These fields help clients to determine if and how to use PAR during user authentications. The relevant new metadata fields are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;pushed_authorization_request_endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The endpoint where the client sends the PAR request.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;require_pushed_authorization_requests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A boolean parameter that indicates whether the authorization server only accepts authorization requests via PAR. If omitted, the default value is false&lt;/p&gt;
&lt;p&gt;For example, in the discovery document from IdentityServer located at &lt;strong&gt;/.well-known/openid-configuration&lt;/strong&gt;, you might see the following metadata:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;pushed_authorization_request_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://myidentityserver.com/connect/par&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;require_pushed_authorization_requests&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a client starts up, it typically downloads this discovery document. By reading these fields, the client can decide whether to use PAR for the authorization request. If &lt;strong&gt;require_pushed_authorization_requests&lt;/strong&gt; is &lt;strong&gt;true&lt;/strong&gt;, then the client must use PAR; otherwise, it can use the standard front-channel authorization request approach.&lt;/p&gt;
&lt;h2 id=&quot;configuring-pushed-authorization-requests-in-identityserver&quot;&gt;Configuring Pushed Authorization Requests in IdentityServer&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://duendesoftware.com/&quot;&gt;&lt;strong&gt;IdentityServer&lt;/strong&gt;&lt;/a&gt; is one of several authorization servers that support PARs. You can configure and control PAR in two key areas: globally during server startup and at the client level in the client definitions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Global Configuration at Startup&lt;/strong&gt;&lt;br&gt;
When setting up the server, you can enable and configure PAR globally in IdentityServer by modifying the &lt;strong&gt;IdentityServerOptions&lt;/strong&gt;. Here’s how you can do it:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Endpoints.EnablePushedAuthorizationEndpoint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.PushedAuthorization &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PushedAuthorizationOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Specifies whether pushed authorization requests are globally required&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // (Default false).&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Required &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // The pushed authorization request&apos;s lifetime (Default 10 minutes)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Lifetime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; *&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 60&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Specifies whether clients may use redirect URIs that were not previously&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // (default false)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        AllowUnregisteredPushedRedirectUris &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;**Client-Specific Configuration&lt;br&gt;
**In addition to global settings, you can configure PAR behavior in the client definitions. This allows you to enforce or adjust PAR usage for specific clients as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    RequirePushedAuthorization &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    PushedAuthorizationLifetime &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;60&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// 10 minutes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configuring-pushed-authorization-requests-in-aspnet-core-9&quot;&gt;Configuring Pushed Authorization Requests in ASP.NET Core 9&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0&quot;&gt;ASP.NET Core 9&lt;/a&gt;&lt;/strong&gt; introduces Pushed Authorization Requests (PAR) support in its OpenID Connect authentication handler. You can manage how PAR is used using the new &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.openidconnect.openidconnectoptions?view=aspnetcore-9.0&quot;&gt;&lt;strong&gt;PushedAuthorizationBehavior&lt;/strong&gt;&lt;/a&gt; option.&lt;/p&gt;
&lt;p&gt;Here’s an example of how to configure it:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddMyOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://myidentityserver.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.PushedAuthorizationBehavior &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; PushedAuthorizationBehavior.Disable;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;PushedAuthorizationBehavior&lt;/strong&gt; option also determines whether and when PAR should be used during the authorization process. This option accepts an &lt;strong&gt;enum&lt;/strong&gt; with the following values:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; enum&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PushedAuthorizationBehavior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// Use Pushed Authorization (PAR) if the PAR endpoint is available.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// This is the default value.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;   UseIfAvailable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// Never use Pushed Authorization (PAR), even if the PAR endpoint is available&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;   Disable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// Always use Pushed Authorization (PAR), and emit errors if there is no PAR endpoint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;   /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;   Require&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By adjusting this option, you can fine-tune how your application handles authorization requests, enabling both strong security and compatibility with identity providers.&lt;/p&gt;
&lt;p&gt;In the next section, we’ll explore how PAR requests work in action.&lt;/p&gt;
&lt;h2 id=&quot;pushed-authorization-requests-in-action&quot;&gt;Pushed Authorization Requests in Action&lt;/h2&gt;
&lt;p&gt;The authorization process involves multiple steps when you’re using Pushed Authorization Requests (PAR), starting with the initial request that pushes the authorization parameters to the authorization server.&lt;/p&gt;
&lt;h3 id=&quot;first-request--the-pushed-authorization-request&quot;&gt;First Request – The Pushed Authorization Request&lt;/h3&gt;
&lt;p&gt;First, the ASP.NET Core OpenIdConnect handler sends a POST request to the identity provider’s PAR endpoint with the authentication details:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;the initial First Request – The Pushed Authorization Request when using PAR&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;622&quot; src=&quot;https://nestenius.se/_astro/par-back-channel-post-request-uri-flow-diagram.lwAAHNcU_Z1Fp3tJ.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s an example of what that request looks like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://myidentityserver.com/connect/par &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;User-Agent&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Microsoft ASP.NET Core OpenIdConnect handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/x-www-form-urlencoded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=localhost-addoidc-client-PAR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid+profile+email+offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ZBSpp9z-eMxb3uFOyl8lMLIW7TGERoprxvBH8wMQ444&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge_method=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_mode=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;form_post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638645126122011768.MGI2YTg4MmItODA0Ni00NzY2LW...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd3_JvuMKE37whAZ6E15FtjzZ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_secret=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;mysecret&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This request closely mirrors the parameters used in the traditional (non-PAR) flow, with one key difference: instead of sending the parameters in the URL, they are sent directly from the client in the POST request body.&lt;/p&gt;
&lt;p&gt;By pushing the parameters in the body of a POST request, PAR provides several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Larger Payload&lt;/strong&gt;:&lt;br&gt;
Unlike URLs, which are limited in length, the body of a POST request can carry a larger payload, making it ideal for more complex authorization requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improved Security&lt;/strong&gt;:&lt;br&gt;
Since the request parameters, including sensitive data (such as client_secret and code_challenge), are placed in the body rather than the URL, this information is less likely to be logged or exposed. This helps to mitigate the risk of leaking secrets through URL logs or browser history.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The response to this POST request typically looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 201&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/json; charset=UTF-8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;request_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;urn:ietf:params:oauth:request_uri:ED3A57A0CC571379715FBE0C0B62DEF84D903A96B8E1A1D43EDC1D757C828050&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;expires_in&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;600&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this response:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;request_uri:&lt;/strong&gt;&lt;br&gt;
A unique identifier (or token) that represents the authorization request. This URI will be used in the subsequent requests to the authorization server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;expires_in:&lt;/strong&gt;&lt;br&gt;
The duration (in seconds) for which the request_uri is valid. In this example, it’s valid for 600 seconds (10 minutes).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;strong&gt;request_uri&lt;/strong&gt; serves as a reference to the original authorization request sent to the PAR endpoint. This allows the client to send only the &lt;strong&gt;request_uri&lt;/strong&gt; over the front channel, keeping sensitive data securely in the back channel and reducing the risk of exposure.&lt;/p&gt;
&lt;h3 id=&quot;second-request--the-authorization-request&quot;&gt;Second Request – The Authorization Request&lt;/h3&gt;
&lt;p&gt;In the second step of the PAR flow, the user’s browser is redirected to the authorization endpoint. Instead of including the full authorization parameters in the URL (as in the traditional flow), the client simply uses the &lt;strong&gt;request_uri&lt;/strong&gt; returned earlier.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Second Request – The Authorization Request in the Pushed Authorization Requests flow.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;999&quot; height=&quot;807&quot; src=&quot;https://nestenius.se/_astro/par-browser-redirect-request-uri-identity-server-flow.BHgUwJV__ZvIxe3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s an example of what this request looks like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://myidentityserver.com/connect/authorize?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=localhost-addoidc-client-PAR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;request_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3A9A7990D0AC4D844FBDB49C9451A23A8C5D41FBCC29BCF1BE201C3338C7099E5D&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-SKU=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ID_NET9_0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-ver=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;8.1.2.0 HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;User-Agent&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Mozilla/5.0 (Windows NT 10.0; Win64; x64)...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Referer&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://localhost:5001/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this request, the &lt;strong&gt;request_uri&lt;/strong&gt; refers to the authorization data previously submitted through the back channel in the first request.&lt;/p&gt;
&lt;p&gt;Once the user is redirected, they will go through the usual authentication and consent steps. Upon successful completion, the authorization server will send the &lt;strong&gt;authorization code&lt;/strong&gt; to the client’s redirect URI:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The remaining steps in the Pushed Authorization Requests flow.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;741&quot; src=&quot;https://nestenius.se/_astro/oidc-authorization-code-flow-browser-identity-server-clien-2.CDcnLCvf_Z1qUW0.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sample request to the &lt;strong&gt;/signin-oidc&lt;/strong&gt; endpoint:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://localhost:5001/signin-oidc &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Content-Type&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; application/x-www-form-urlencoded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code=7A9404B7DA1532A0D836834263AE73CF029AFE819D4AE0A62079C23F6C6452BF-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid+profile+email+offline_access&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;CfDJ8IgPXRNAZH1EkNA0dd3_Jvv5huWjaDM6DMPX...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;session_state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;YiFCBw1ATTffMuEI0SFOPwjnAoorH3Jz_...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;iss=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Fmyidentityserver.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this code, the OpenID Connect handler can then exchange it for the real tokens, just like how we did it in the non-PAR example.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this blog post, we explored Pushed Authorization Requests (PAR) in ASP.NET Core 9 and how it improves the security of OAuth and OpenID Connect (OIDC) flows. PAR moves sensitive authorization parameters from the browser (front channel) to secure back-channel communication instead, offering several key benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improved Security&lt;/strong&gt;: Keeps sensitive data out of URLs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tamper Resistance&lt;/strong&gt;: Prevents attackers from altering authorization parameters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cleaner, Shorter URLs&lt;/strong&gt;: Especially useful in complex authorization scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using Duende IdentityServer as an example, we showed how PAR is configured globally and at the client level. Additionally, we walked through the PAR process, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Pushed Authorization Request, where authorization data is securely sent to the server.&lt;/li&gt;
&lt;li&gt;The Authorization Request, where the client uses the returned &lt;strong&gt;request_uri&lt;/strong&gt; to complete the authorization flow.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/&quot;&gt;OpenIdConnectHandler adds support for Pushed Authorization Requests (PAR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc9126&quot;&gt;RFC-9126 OAuth 2.0 Pushed Authorization Requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/identityserver/v7/tokens/par/&quot;&gt;IdentityServer Pushed Authorization Requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/51686&quot;&gt;Support for Pushed Authorization Requests in OidcHandler API Proposal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/&quot;&gt;BearerToken: The new Authentication handler in ASP.NET Core 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker – Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/building-asp-net-core-apis/&quot;&gt;Building ASP.NET Core APIs&lt;/a&gt; &lt;img alt=&quot;Building ASP.NET Core APIs - new training workshop&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;44&quot; height=&quot;16&quot; src=&quot;https://nestenius.se/_astro/new-badge-yellow-label.BuswNKOb_Z8ur7o.webp&quot; srcset=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-openid-connect-and-oauth/&quot;&gt;Introduction to OpenID Connect and OAuth&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Introducing the Cloud Debugger for Azure</title><link>https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/</link><guid isPermaLink="true">https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/</guid><description>The Cloud Debugger is an open-source tool for Azure developers to explore, learn, and troubleshoot their Azure cloud environments. Whether preparing for</description><pubDate>Mon, 21 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Cloud Debugger is an open-source tool for Azure developers to explore, learn, and troubleshoot their Azure cloud environments. Whether preparing for Azure certification, looking to streamline debugging, or aiming to deepen your understanding of Azure, Cloud Debugger provides the tools to make the cloud more discoverable.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Examples of all the tools and Services that the Azure Cloud Debugger supports and provides.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;702&quot; src=&quot;https://nestenius.se/_astro/azure-cloud-debugger-robot-features-services-diagram.CHRD7_jJ_Z1IjDIU.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;background---getting-azure-certified&quot;&gt;Background - Getting Azure certified&lt;/h2&gt;
&lt;p&gt;As I prepared for the &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/credentials/certifications/azure-developer/&quot;&gt;AZ-204 Azure developer certification&lt;/a&gt;&lt;/strong&gt;, I realized that I needed to build something larger to understand the Azure Cloud and the services covered by the Azure Developer certification. Just doing the included exercises was not enough. This led to me creating this open-source tool designed to help learn and simplify the debugging process for Azure applications.&lt;/p&gt;
&lt;h2 id=&quot;the-mars-rover-vs-azure-analogy&quot;&gt;The Mars Rover vs. Azure Analogy&lt;/h2&gt;
&lt;p&gt;When we explore Mars, we send rovers to measure, observe, and perform experiments to understand the unknown.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Image of the Mars rover&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;980&quot; height=&quot;695&quot; src=&quot;https://nestenius.se/_astro/The-Mars-Rover-Analogy.v82EWe5q_Z11TyP6.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Developers also need a systematic approach when navigating Azure’s vast and complex cloud environment. Cloud Debugger is designed to provide this by enabling you to explore, experiment, and gain deeper insights into your cloud setup.&lt;/p&gt;
&lt;h2 id=&quot;cloud-debugger-as-a-teaching-tool-for-azure&quot;&gt;Cloud Debugger as a Teaching Tool for Azure&lt;/h2&gt;
&lt;p&gt;A secondary purpose of this tool is to serve as an exercise platform for future Azure training. Rather than relying solely on theoretical exercises, Cloud Debugger offers a hands-on environment where developers can actively engage with Azure services, troubleshoot real-world scenarios, and experiment with different configurations, features, and concepts.&lt;/p&gt;
&lt;h2 id=&quot;what-can-the-cloud-debugger-for-azure-do&quot;&gt;What can the Cloud Debugger for Azure do?&lt;/h2&gt;
&lt;p&gt;The Cloud Debugger offers over 30 specialized tools to help developers explore, diagnose, and understand their Azure environment. These tools include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h3 id=&quot;http-request-tools&quot;&gt;&lt;strong&gt;HTTP Request tools&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Current request viewer&lt;br&gt;
View details about the current request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Request Logger&lt;br&gt;
Provides a list of all the received requests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Calling External APIs&lt;br&gt;
Make HTTP requests to external APIs. Useful for testing external communication and tracing in Application Insights.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&quot;azure-app-services&quot;&gt;&lt;strong&gt;Azure App Services&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;File System&lt;br&gt;
Explore the local file system.&lt;/li&gt;
&lt;li&gt;Local Cache&lt;br&gt;
Explore the App Services local cache feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&quot;azure-storage-tools&quot;&gt;&lt;strong&gt;Azure Storage tools&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Blob Storage Tools&lt;br&gt;
Read and create blobs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a user delegation SAS token&lt;br&gt;
Create a sample token to access a blob in blob storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File System Explorer&lt;br&gt;
Explore the local file system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Redis Explorer&lt;br&gt;
Write and read keys from Redis.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cosmos DB&lt;br&gt;
(coming soon)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h3 id=&quot;azure-events-and-messaging&quot;&gt;&lt;strong&gt;Azure Events and Messaging&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Event Grid&lt;br&gt;
Send events to Event Grid&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Event Hubs&lt;br&gt;
Produce and consume Event Hub events&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WebHooks&lt;br&gt;
Receive webhook requests from EventGrid and other services.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Identity Tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DefaultAzureCredentials&lt;br&gt;
Explore the inner workings of this TokenCredential.&lt;/li&gt;
&lt;li&gt;Token caching&lt;br&gt;
Explore how the token caching varies across the different Token Credentials.&lt;/li&gt;
&lt;li&gt;Token Credentials Explorer&lt;br&gt;
Explore the different Token Credentials.&lt;/li&gt;
&lt;li&gt;Azure.Identity event log viewer&lt;br&gt;
Explore and view the internal event log.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Diagnostics and system information&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Runtime Details&lt;br&gt;
Show details about the current runtime and operating system.&lt;/li&gt;
&lt;li&gt;Environment Variables&lt;br&gt;
View all the environment variables.&lt;/li&gt;
&lt;li&gt;Errors&lt;br&gt;
Trigger different error pages.&lt;/li&gt;
&lt;li&gt;Network Details&lt;br&gt;
Show details about the network interfaces.&lt;/li&gt;
&lt;li&gt;Scale-out and load balancing&lt;br&gt;
Visualize the effects of load-balancing&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Logging&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging&lt;br&gt;
Write a message to the standard log&lt;/li&gt;
&lt;li&gt;Log Analytics Workspace&lt;br&gt;
Write messages to this service.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Other tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Caching and CDN&lt;br&gt;
Explore the effect of caching and CDN.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Health checks&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More tools will be added over time. If you have suggestions, feel free to contact the author, submit an &lt;a href=&quot;https://github.com/tndata/CloudDebugger/issues&quot;&gt;&lt;strong&gt;issue&lt;/strong&gt;&lt;/a&gt;, or contribute via a &lt;strong&gt;&lt;a href=&quot;https://github.com/tndata/CloudDebugger/pulls&quot;&gt;pull request&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;deploying-cloud-debugger-for-azure&quot;&gt;Deploying Cloud Debugger for Azure&lt;/h2&gt;
&lt;p&gt;The Cloud Debugger is also designed to be highly flexible in its deployment options. The same service can be deployed across multiple environments using the provided PowerShell scripts, which makes it versatile for various use cases. It supports the following target environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Azure App Services&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Linux Containers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Container Instances&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Container Apps&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This allows the service to be deployed across multiple environments, enabling comparisons of application behavior in each. One PowerShell script, &lt;strong&gt;BuildAndDeployAll.ps1&lt;/strong&gt;, will build and deploy to all five environments in one operation.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the Cloud Debugger is deployed, supporting Azure App Services, Container Instances and Container Apps&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;507&quot; src=&quot;https://nestenius.se/_astro/cloud-debugger-powershell-azure-deployment-targets-diagram.8_Hd1hZ3_sPpnx.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Equally important is the &lt;strong&gt;DeleteAllResources.ps1&lt;/strong&gt; script, which will delete the Cloud Debugger resource group and its content. I typically run this script at the end of the day to save money and resources.&lt;/p&gt;
&lt;h3 id=&quot;&quot;&gt;&lt;/h3&gt;
&lt;p&gt;Why are we using PowerShell to deploy?&lt;/p&gt;
&lt;p&gt;Mastering Azure CLI was essential for the AZ-204 certification, and using PowerShell provided hands-on experience with low-level deployment. This approach prepares me to transition to higher-level tools like &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview&quot;&gt;Azure Bicep&lt;/a&gt;, which I plan to explore by converting these scripts in the future.&lt;/p&gt;
&lt;h2 id=&quot;cloud-debugger-implementation-details&quot;&gt;Cloud Debugger Implementation Details&lt;/h2&gt;
&lt;p&gt;The Debugger is implemented as a standard &lt;strong&gt;ASP.NET Core 8&lt;/strong&gt; application written in &lt;strong&gt;C#&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;continious-integration&quot;&gt;Continious Integration&lt;/h3&gt;
&lt;p&gt;A GitHub Action is triggered with every commit to automatically verify that the project builds correctly. However, we currently handle deployment to Azure using local PowerShell scripts, rather than deploying directly from GitHub.&lt;/p&gt;
&lt;h3 id=&quot;customized-aspnet-core-libraries&quot;&gt;Customized ASP.NET Core Libraries&lt;/h3&gt;
&lt;p&gt;We pulled down the source code for Azure.Identity and HttpLogging then converted them into custom class libraries to get better insights and integration with this tools. We also tweaked the namespaces of these two libraries to avoid naming collisions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**&lt;a href=&quot;https://www.nuget.org/packages/Azure.Identity&quot;&gt;Azure.Identity&lt;/a&gt;&lt;br&gt;
**We patched this library to gain better insight into how the different Token Credentials behave internally. You can see this when you use the various Identity tools there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft.AspNetCore.HttpLogging&lt;/strong&gt;&lt;br&gt;
This ASP.NET Core class library has been patched to improve the logging of incoming requests to the application, providing better insights into request details.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;secure-development-practices&quot;&gt;Secure Development Practices&lt;/h2&gt;
&lt;p&gt;We try to follow secure development practices, using tools that run with each pull request to prevent bugs, code smells, and security vulnerabilities from entering the cloud environment.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Secure development practices, including SonarCloud, Dependabot..&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;658&quot; src=&quot;https://nestenius.se/_astro/Secure-development-practices-1024x658.BA0OlS8t_Z1hCW65.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The tools we use are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://sonarcloud.io/&quot;&gt;&lt;strong&gt;SonarCloud&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Provides cloud-based static code analysis to detect code quality issues and vulnerabilities.&lt;/li&gt;
&lt;li&gt;**&lt;a href=&quot;https://www.sonarsource.com/products/sonarlint/&quot;&gt;SonarLint&lt;/a&gt;&lt;br&gt;
**A lightweight IDE extension that helps catch code issues in real-time during development.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide&quot;&gt;&lt;strong&gt;GitHub Dependabot.&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Automatically updates dependencies to help you avoid security vulnerabilities and outdated libraries.&lt;/li&gt;
&lt;li&gt;**&lt;a href=&quot;https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning&quot;&gt;GitHub Code Scanning&lt;/a&gt;&lt;br&gt;
**Identifies potential security issues and bugs in the code by scanning the repository.&lt;/li&gt;
&lt;li&gt;**&lt;a href=&quot;https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning&quot;&gt;GitHub Secrets Scanning&lt;/a&gt;&lt;br&gt;
**Detects exposed secrets and sensitive data in the repository to prevent leaks and security breaches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is what it can look like when a pull request does not pass all the checks:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example GitHub pull request that fails the code scanning&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;424&quot; src=&quot;https://nestenius.se/_astro/github-pull-request-checks-sonarcloud-codeql-docker.tsKB_ZIa_ieSMo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;important-azure-security-consideration&quot;&gt;Important Azure Security Consideration&lt;/h2&gt;
&lt;p&gt;It is important not to run this tool in your production environment because it can expose internal secrets, keys, access tokens, and other sensitive information. The tool can also create or send content to various Azure services, which could lead to unintended changes or security risks if it is misused.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Cloud Debugger for Azure warning sign.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;1024&quot; src=&quot;https://nestenius.se/_astro/Important-Security-Consideration.BVcnvsME_Z1LeJKI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Crucially, you must lock down this tool to ensure that only authorized users can access it. Currently, the tool does not provide any built-in feature to restrict access, so additional security measures must be implemented separately.&lt;/p&gt;
&lt;h2 id=&quot;getting-started-with-the-cloud-debugger-for-azure&quot;&gt;Getting Started with the Cloud Debugger for Azure&lt;/h2&gt;
&lt;p&gt;The tool is now available on &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;&lt;strong&gt;GitHub&lt;/strong&gt;&lt;/a&gt;; you can start using it by following the &lt;strong&gt;&lt;a href=&quot;https://github.com/tndata/CloudDebugger/wiki/Deployment&quot;&gt;deployment guide&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The extensive &lt;strong&gt;&lt;a href=&quot;https://github.com/tndata/CloudDebugger/wiki&quot;&gt;documentation&lt;/a&gt;&lt;/strong&gt; offers everything you need to get up and running quickly, focusing on providing the shortest path to value.&lt;/p&gt;
&lt;h2 id=&quot;join-us-feedback-and-contribute-to-the-project&quot;&gt;Join Us, Feedback and Contribute to the project&lt;/h2&gt;
&lt;p&gt;Cloud Debugger is open source, and we welcome contributions! Whether you’re interested in adding new features, fixing bugs, or sharing your experiences with us and the community, we’d love to hear from you. Check out our &lt;strong&gt;&lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;GitHub repository&lt;/a&gt;&lt;/strong&gt; and join our growing community there.&lt;/p&gt;
&lt;h2 id=&quot;other-blog-posts-by-me&quot;&gt;Other Blog Posts by Me&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;Deploy containers Azure App Services using user-assigned managed identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;Discovering .NET codebases using code coverage and NCrunch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt; &lt;img alt=&quot;New training workshop - Service Communication - REST vs. GrapQL vs. gRPC&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;44&quot; height=&quot;16&quot; src=&quot;https://nestenius.se/_astro/new-badge-yellow-label.BuswNKOb_Z8ur7o.webp&quot; srcset=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;azure-challenges-im-here-to-help&quot;&gt;Azure Challenges? I’m Here to Help!&lt;/h2&gt;
&lt;p&gt;Considering using Azure cloud or have a challenge or project on your hands? Need to pave a clear and helpful path for improving cloud skills in your team? If you need some guidance, I’d be happy to help you! I can help you with: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigating Azure cloud migration and deployment challenges. &lt;/li&gt;
&lt;li&gt;Overcoming inefficiencies, debugging and visibility issues in Azure cloud.&lt;/li&gt;
&lt;li&gt;Defining a training pathway for your team to harness Azure effectively.&lt;/li&gt;
&lt;li&gt;Identifying security risks and securing your environment more broadly.&lt;/li&gt;
&lt;li&gt;Organizing projects, testing, production and deployment in Azure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feel free to get in touch using the details below, submitting a form on the contact page, or connect with me on LinkedIn.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;mailto:tore@tn-data.se&quot;&gt;tore@tn-data.se&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+46 708 166856&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/in/torenestenius/&quot;&gt;Linkedin&lt;/a&gt; Contact&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>azure</category></item><item><title>User Delegation SAS Tokens In Azure Explained</title><link>https://nestenius.se/azure/user-delegation-sas-tokens-in-azure-explained/</link><guid isPermaLink="true">https://nestenius.se/azure/user-delegation-sas-tokens-in-azure-explained/</guid><description>I discovered many interesting Azure features while studying for the AZ-204 certification. One of these features is User Delegation SAS tokens, a way to</description><pubDate>Fri, 04 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I discovered many interesting Azure features while studying for the &lt;a href=&quot;https://learn.microsoft.com/en-us/credentials/certifications/azure-developer&quot;&gt;&lt;strong&gt;AZ-204 certification&lt;/strong&gt;&lt;/a&gt;. One of these features is &lt;strong&gt;User Delegation SAS tokens&lt;/strong&gt;, a way to provide access to Azure Blob Storage. In this blog post, I’ll share what I learned about these tokens and how to use them in C#/.NET.&lt;/p&gt;
&lt;h2 id=&quot;accessing-azure-storage-accounts&quot;&gt;Accessing Azure Storage Accounts&lt;/h2&gt;
&lt;p&gt;Azure offers various methods to secure, manage, and grant access to storage accounts, from anonymous access to more advanced approaches like Azure Role-Based Access Control (RBAC). Each method has its use case, security level, and limitations.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Authenticating to Azure storage account: anonymous access, managed identities, access keys, and SAS keys.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;375&quot; src=&quot;https://nestenius.se/_astro/azure-storage-account-access-methods-diagram.ByXumFf9_RXXTK.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;sas-shared-access-signature-tokens&quot;&gt;SAS (Shared Access Signature tokens)&lt;/h2&gt;
&lt;p&gt;With Shared Access Signatures (SAS), you can securely delegate access to resources within your Azure Storage accounts. They allow you to specify which resources can be accessed, what actions can be taken, and how long the permissions should be valid.&lt;/p&gt;
&lt;p&gt;For example, &lt;strong&gt;if&lt;/strong&gt; you have a blob in storage named &lt;strong&gt;&lt;em&gt;Myblob.txt&lt;/em&gt;&lt;/strong&gt; and want to grant access to users outside of Azure, you can use SAS tokens.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A storage account containing a single blob container with a blob named MyBlob.txt inside.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;248&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/azure-blob-storage-container-with-myblob-txt.BfItwDTW_2o3PzS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To do this, you select the blob and specify the desired access level and expiration date, as shown in the image below:&lt;/p&gt;
&lt;p&gt;As a result, Azure will generate an SAS token that might look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sp=r&amp;#x26;st=2024-08-21T17:17:21Z&amp;#x26;se=2024-08-22T01:17:21Z&amp;#x26;spr=https&amp;#x26;sv=2022-11-02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sr=b&amp;#x26;sig=T7mWwTfZnvtU%2FuoR1vDAjKX1B7OLl0b6YcHgiJ0SFBw%3D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;Example of how to define a SAS access token in an Azure Storage Account.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;679&quot; height=&quot;608&quot; src=&quot;https://nestenius.se/_astro/azure-portal-blob-sas-token-generation-account-key.BkYZFHNA_Z1s0Bro.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This token is signed using one of the two access keys associated with the storage account.&lt;/p&gt;
&lt;p&gt;Then, you can append this token to the blob resource URL, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://clouddebuggerstorage.blob.core.windows.net/Data/Myblob.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;?sp=r&amp;#x26;st=2024-08-21T17:17:21Z&amp;#x26;se=2024-08-22T01:17:21Z&amp;#x26;spr=https&amp;#x26;sv=2022-11-02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sr=b&amp;#x26;sig=T7mWwTfZnvtU%2FuoR1vDAjKX1B7OLl0b6YcHgiJ0SFBw%3D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anyone with this URL can now read the blob from anywhere in the world until the token expires.&lt;/p&gt;
&lt;p&gt;Awesome!&lt;/p&gt;
&lt;h2 id=&quot;the-issue-with-using-access-keys&quot;&gt;The issue with using access keys&lt;/h2&gt;
&lt;p&gt;Azure provides two &lt;strong&gt;access keys&lt;/strong&gt; for each storage account, often named &lt;strong&gt;key1&lt;/strong&gt; and &lt;strong&gt;key2&lt;/strong&gt;. These keys provide full access to all services enabled in the storage account, &lt;strong&gt;including&lt;/strong&gt; Blob Storage, File Storage, Queue Storage, and Table Storage. The reason for having two keys is to facilitate key rotation.&lt;/p&gt;
&lt;p&gt;These two &lt;strong&gt;highly sensitive keys&lt;/strong&gt; should never be used outside your storage account because they provide full admin access to the entire storage account. You will find these two keys in the Azure Portal under the &lt;strong&gt;Access keys&lt;/strong&gt; blade:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;An Azure Storage Account contains two secret administrative access keys.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;328&quot; height=&quot;230&quot; src=&quot;https://nestenius.se/_astro/azure-storage-account-access-keys-diagram.BkE-n_E7_Z19VRCU.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can create an SAS token by signing the desired access request with one of these keys.&lt;/p&gt;
&lt;p&gt;With this concept, you can issue any number of SAS keys with different access levels using the same access key:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Examples of access keys and connection strings within an Azure Storage Account.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;612&quot; height=&quot;354&quot; src=&quot;https://nestenius.se/_astro/azure-storage-account-access-keys-key1-key2.DMZq76XM_Z1Wgs5h.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;An access key, combined with the desired access levels and permissions resulting in a SAS Key&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;504&quot; src=&quot;https://nestenius.se/_astro/azure-storage-access-key-plus-desired-access-generates-sas-k.C8nPlW_O_Z1YSOr7.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;Azure Storage Account access key generating multiple SAS Keys/tokens&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;722&quot; src=&quot;https://nestenius.se/_astro/azure-storage-access-key-generating-multiple-sas-keys-diagra.wTWvUsVQ_lA2K1.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-problem-with-sas-tokens&quot;&gt;What is the problem with SAS tokens?&lt;/h2&gt;
&lt;p&gt;Although SAS tokens provide essential access controls for Azure storage, they come with certain challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because SAS tokens are signed with the storage account’s access keys, all issued SAS tokens are jeopardized if these keys are exposed or compromised.&lt;/li&gt;
&lt;li&gt;Furthermore, managing SAS tokens can become complex in dynamic environments, especially when customizing access for a growing number of users and applications.&lt;/li&gt;
&lt;li&gt;If someone loses or leaks a SAS token, anyone who obtains it can access the storage resources it governs until the token expires or you manually revoke it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;An access key can be used to generate multiple SAS keys,&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;591&quot; height=&quot;280&quot; src=&quot;https://nestenius.se/_astro/azure-storage-account-access-key-generating-sas-keys-diagram.DwI7eLz7_1UDCWi.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;These limitations underscore the need for a more flexible solution, such as &lt;strong&gt;User Delegation SAS tokens&lt;/strong&gt;, which integrate with Azure Active Directory for enhanced security and manageability.&lt;/p&gt;
&lt;h2 id=&quot;user-delegation-sas-tokens&quot;&gt;User Delegation SAS tokens&lt;/h2&gt;
&lt;p&gt;What is the main difference compared to the SAS tokens described above?&lt;/p&gt;
&lt;p&gt;The key advantage of User Delegation SAS tokens is that they allow applications to issue their own SAS tokens, offering more granular and secure access controls. This enables applications to define permissions tailored to each user or scenario without relying on the storage account’s access keys.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How an app can aquire an User Delegation SAS token to issue SAS keys.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;280&quot; src=&quot;https://nestenius.se/_astro/azure-storage-account-user-delegation-key-per-app-diagram.DIbNKJ2q_2ctr53.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;how-does-the-application-obtain-the-user-delegation-key&quot;&gt;How does the application obtain the user delegation key?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;To start&lt;/strong&gt;, the application uses its &lt;strong&gt;managed identity&lt;/strong&gt; to authenticate with Entra ID and acquire an access token. Once authenticated, the BlobServiceClient utilizes this access token to request a &lt;strong&gt;user delegation key&lt;/strong&gt; from Entra ID.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; blobStorageUri&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; $&quot;https://&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;StorageAccountName&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.blob.core.windows.net&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Step #1, get an authentication token from Entra ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; credentials&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Step #2, Create a BlobServiceClient with these credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(blobStorageUri), credentials);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Step #3, get a user delegation key from Entra ID&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; startsOn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DateTimeOffset.UtcNow.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddMinutes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// To avoid clock skew issues&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; expiresOn&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; startsOn.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddDays&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);                 &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Max is 7 days&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; userDelegationKey&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; client.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetUserDelegationKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(startsOn, expiresOn);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image below illustrates how the application acquires the user delegation key:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Flow of an application requesting a user delegation key by authenticating with Azure AD.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;701&quot; height=&quot;445&quot; src=&quot;https://nestenius.se/_astro/app-entra-id-user-delegation-key-sequence-diagram.CYwXeJvZ_QCtdE.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-the-user-delegation-key&quot;&gt;What is the purpose of the user delegation key?&lt;/h2&gt;
&lt;p&gt;our application can generate and sign new SAS tokens with the User Delegation Key, without relying on the storage account access keys. This provides an additional layer of security by granting granular permissions. The key is tied to the application and the Azure Active Directory (AAD) identity requesting it. Each time you request a User Delegation Key, a new key is issued, ensuring that permissions are limited to the specific context of the request.&lt;/p&gt;
&lt;h2 id=&quot;what-is-inside-the-delegation-key&quot;&gt;What is inside the delegation key?&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;GetUserDelegationKey&lt;/strong&gt; method provides a &lt;a href=&quot;https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/storage/Azure.Storage.Blobs/src/Models/UserDelegationKey.cs&quot;&gt;&lt;strong&gt;UserDelegationType&lt;/strong&gt;&lt;/a&gt; object and the following table shows an example of what is inside this object:&lt;/p&gt;
&lt;h2 id=&quot;creating-a-user-delegation-sas-token&quot;&gt;Creating A User Delegation SAS token&lt;/h2&gt;
&lt;p&gt;With the user delegation key in place, the application can create a new user delegation SAS token using the following code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Step #4, Define the permissions for the SAS token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;BlobSasBuilder&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sasBuilder&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobSasBuilder&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BlobContainerName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; containerName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BlobName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; blobName,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Resource &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;b&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    StartsOn &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DateTimeOffset.UtcNow,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ExpiresOn &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DateTimeOffset.UtcNow.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddHours&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Step #5, Specify the necessary permissions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sasBuilder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SetPermissions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(BlobSasPermissions.Read);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//// Alternatively, you can combine multiple permissions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//sasBuilder.SetPermissions(BlobContainerSasPermissions.Read |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//                          BlobContainerSasPermissions.Write |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//                          BlobContainerSasPermissions.List |&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//                          BlobContainerSasPermissions.Delete);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Step #6, Build the SAS token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SASToken&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sasBuilder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToSasQueryParameters&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(userDelegationKey,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                               storageAccountName).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The SAS token that you generate might look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;skoid=1dc7a951-446c-4572-8275-30561ef281f7&amp;#x26;sktid=567d82a1-7f61-4da2-b955-d3244ea6e976&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;skt=2024-08-22T08%3A01%3A16Z&amp;#x26;ske=2024-08-23T08%3A01%3A16Z&amp;#x26;sks=b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;skv=2024-08-04&amp;#x26;sv=2024-08-04&amp;#x26;st=2024-08-22T08%3A02%3A16Z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;se=2024-08-22T09%3A02%3A16Z&amp;#x26;sr=b&amp;#x26;sp=r&amp;#x26;sig=zajbqclywkZvs56T0RIMqcgWa24FHXpFdogV%2F9KMDIQ%3D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we reformat it, we see the following details&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;skoid=1dc7a951-446c-4572-8275-30561ef281f7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sktid=567d82a1-7f61-4da2-b955-d3244ea6e976&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;skt=2024-08-22T08%3A01%3A16Z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;ske=2024-08-23T08%3A01%3A16Z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sks=b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;skv=2024-08-04&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sv=2024-08-04&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;st=2024-08-22T08%3A02%3A16Z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;se=2024-08-22T09%3A02%3A16Z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sr=b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sp=r&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x26;sig=zajbqclywkZvs56T0RIMqcgWa24FHXpFdogV%2F9KMDIQ%3D&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The details for what each parameter represents can be found &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas&quot;&gt;here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;using-the-user-delegated-sas-token&quot;&gt;Using the user-delegated SAS token&lt;/h2&gt;
&lt;p&gt;With the token in place, you can now, for example, append it to blog storage URLs like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://[MyStorage].blob.core.windows.net/clouddebugger/MyBlob.txt?[SASToken]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The alternative is to pass it as credentials to the &lt;strong&gt;BlobServiceClient&lt;/strong&gt;, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(SASToken);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;user-delegation-sas-token-permissions&quot;&gt;User Delegation SAS Token permissions&lt;/h2&gt;
&lt;p&gt;For an application to be able to issue a User Delegation SAS token, it must be granted specific permissions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To get the delegation key, the application needs one of the following roles assigned against the target storage account:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Contributor&lt;/li&gt;
&lt;li&gt;Storage Account Contributor&lt;/li&gt;
&lt;li&gt;Storage Blob Data Contributor&lt;/li&gt;
&lt;li&gt;Storage Blob Data Owner&lt;/li&gt;
&lt;li&gt;Storage Blob Data Reader&lt;/li&gt;
&lt;li&gt;Storage Blob Delegator&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These roles should be scoped to the storage account, resource group, or subscription level. For more detailed information, visit the official documentation &lt;a href=&quot;https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas#assign-permissions-with-rbac&quot;&gt;&lt;em&gt;here&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Additionally, the application must possess RBAC permissions to interact with specific resources within the storage account, such as containers or blobs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-are-the-benefits-of-using-user-delegation-sas-tokens&quot;&gt;What are the Benefits of Using User Delegation SAS Tokens?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key Management&lt;/strong&gt;The key used to sign the SAS tokens is obtained from Entra ID using the application’s managed identity, eliminating the need to manage the signing key manually.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited Lifetime:&lt;/strong&gt;&lt;br&gt;
You can configure the tokens to be short-lived, which reduces the window of opportunity for unauthorized access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced Auditing:&lt;/strong&gt;&lt;br&gt;
User Delegation SAS tokens appear in Azure Storage access logs, improving the traceability of access and recorded actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Principle of Least Privilege:&lt;/strong&gt;&lt;br&gt;
Applications issuing these tokens can operate with lower privileges, adhering to the principle of least privilege, which reduces their risk profile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access Limitation:&lt;/strong&gt;&lt;br&gt;
A SAS token cannot grant more access to the storage account than the permissions allocated to the application that issued it, ensuring that privileges are appropriately contained.&lt;/li&gt;
&lt;li&gt;**Security Against Key Leakage:**By not depending on account keys, User Delegation SAS tokens significantly reduce the risk associated with key leakage. Exposure of account keys can jeopardize the security of the entire storage account; this risk is mitigated with User Delegation SAS tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;When I first encountered the User Delegation SAS tokens concept, I found the technical documentation overwhelming and lacking in clear use-case descriptions. The initial confusion led to some experimentation and, admittedly, a struggle. However, as I dug deeper and started to apply what I learned practically, the pieces began to fall into place.&lt;/p&gt;
&lt;p&gt;Writing this blog post has deepened my understanding of the purpose and applications of User Delegation SAS tokens. I’ve come to appreciate their significance and utility. It’s important to note that User Delegation SAS tokens are limited to Azure Blob Storage.&lt;/p&gt;
&lt;h2 id=&quot;how-can-i-explore-delegation-tokens&quot;&gt;How Can I Explore Delegation Tokens?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/tndata/CloudDebugger&quot;&gt;&lt;strong&gt;Cloud Debugger&lt;/strong&gt;&lt;/a&gt;, an exploration tool for Azure cloud developers, includes a feature that allows you to experiment with delegation tokens. The image below shows an example of what it might look like when using this tool:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Exploring Delegation Tokens in the Cloud Debugger tool&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;819&quot; src=&quot;https://nestenius.se/_astro/azure-blob-storage-user-delegation-sas-token-generated.Dy3RPsCt_EJGc.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-user-delegation-sas-create-dotnet&quot;&gt;Create a user delegation SAS for a blob with .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas&quot;&gt;Create a user delegation SAS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature&quot;&gt;Delegate access by using a shared access signature&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-gb/training/modules/secure-azure-storage-account/&quot;&gt;Secure your Azure Storage account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview&quot;&gt;Grant limited access to Azure Storage resources using shared access signatures (SAS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://azure.microsoft.com/fr-fr/blog/announcing-user-delegation-sas-tokens-preview-for-azure-storage-blobs/&quot;&gt;Announcing user delegation SAS tokens preview for Azure Storage Blobs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/csharp-beyond-the-fundamentals/&quot;&gt;C# – Beyond the Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, If I missed anything or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>azure</category></item><item><title>Running Docker in an Azure Windows Virtual Machine – Not so fast!</title><link>https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/</link><guid isPermaLink="true">https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/</guid><description>This blog post describes getting Docker up and running inside an Azure Windows Virtual Machine. This might sound like a simple task, but trust me, there</description><pubDate>Tue, 24 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post describes getting Docker up and running inside an Azure Windows Virtual Machine. This might sound like a simple task, but trust me, there are some surprises on the way that took me a day or so to figure out.&lt;/p&gt;
&lt;h2 id=&quot;why-run-docker-in-an-azure-virtual-machine&quot;&gt;Why run docker in an Azure Virtual Machine?&lt;/h2&gt;
&lt;p&gt;As a trainer, I need to provide a consistent, pre-configured environment where students can focus on learning without the hassle of setting up their own machines. Many of my exercises rely on Docker, but in environments like government or corporate settings, strict security policies often prevent students from installing Docker Desktop.&lt;/p&gt;
&lt;p&gt;By using Azure virtual machines, I sidestep these restrictions, ensuring that everyone has a uniform setup. This approach also helps avoid issues like local firewalls blocking network requests, which is especially important during security training sessions.&lt;/p&gt;
&lt;p&gt;Best of all, it allows me to focus on delivering the training itself without worrying about technical setup when I arrive at a customer site.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Master Image&lt;/strong&gt;&lt;br&gt;
I update the master image 1-2 times yearly, and the process has worked great! But one day a few years ago, a few days before my next training class, I decided to rebuild the image. I created the virtual machine in the Azure portal asI have always done, like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How a master Azure image is used to create multiple virtual machines for my students.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;476&quot; src=&quot;https://nestenius.se/_astro/azure-master-disc-image-cloned-to-multiple-student-vms.BgsR7lp2_Z1AXT1Q.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;How to create an Azure Virtual Machine in Azure Portal using the Trusted launch virtual machine option&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;685&quot; height=&quot;326&quot; src=&quot;https://nestenius.se/_astro/azure-portal-windows-10-vm-instance-details-configuration.Bl65P9z0_Z1CijUN.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;After the image was created, I logged in to the master machine using a remote desktop, applied all the various updates, and installed the various software packages. All going well until it was time to install Docker. Installing Docker has worked fine in the past, but to my surprise, this time, it did not work! Why? &lt;/p&gt;
&lt;p&gt;Perhaps I missed some steps in my documented process, so I recreated the VM and tried again. However, it still fails with the same error. From previous experience, I know that that installation of Docker can be a bit unreliable. However, it is still not starting after installation!&lt;/p&gt;
&lt;p&gt; I was getting weird errors like this one:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;deploying WSL2 distributions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ensuring main distro &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; deployed&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; deploying &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;docker-desktop&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; importing WSL distro &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;WSL2 is not supported with your current machine configuration&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Please enable the &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Virtual Machine Platform&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; optional component and ensure virtualization &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; enabled&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BIOS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Enable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Virtual&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Machine&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Platform&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&quot; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;by&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; running&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wsl.exe &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;install &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;no&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;distribution&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;For information please visit &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;https&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//aka.ms/enablevirtualization&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Error &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Wsl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Service&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;RegisterDistro&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;CreateVm&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HCS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HCS_E_HYPERV_NOT_INSTALLED&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot; output=&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;desktop&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;: exit code: 4294967295&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;running WSL command wsl.exe &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:\Windows\System32\wsl.exe &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;import docker&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;desktop \AppData\Local\Docker\wsl\main &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;C&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:\Program Files\Docker\Docker\resources\wsl\wsl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;bootstrap.tar &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;version &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;WSL2 &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;is&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; supported&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; with&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; your&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; current&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; machine&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; configuration&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Please&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Virtual&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Machine&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Platform&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&quot; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;optional&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; component&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; and&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ensure&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; virtualization&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; enabled&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BIOS&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Enable&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Virtual&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Machine&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Platform&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&quot; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;by&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; running&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; wsl.exe &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;install &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;no&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;distribution&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;For information please visit &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;https&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//aka.ms/enablevirtualization&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Error &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;code&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Wsl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Service&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;RegisterDistro&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;CreateVm&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HCS&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HCS_E_HYPERV_NOT_INSTALLED&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; exit status &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0xffffffff&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;checking if isocache &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;exists&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: CreateFile \\wsl$\docker&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;desktop&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;data\isocache\&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; The network name cannot be found.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I encountered this issue a few years ago, I didn’t receive an error message as helpful as the one I got today when I tested it for this blog post. The error seems to be related to the BIOS, but since this is a VM, I can’t access the BIOS settings. This feature has worked fine before, so I doubt the BIOS is the problem.&lt;/p&gt;
&lt;p&gt;Additionally, the VM type supports nested virtualization, so that isn’t the issue either.&lt;/p&gt;
&lt;h2 id=&quot;troubleshooting-docker-in-azure-virtual-machine&quot;&gt;Troubleshooting Docker in Azure Virtual Machine&lt;/h2&gt;
&lt;p&gt;Ok, so let’s troubleshoot!&lt;/p&gt;
&lt;p&gt;The first step is to check what Windows features are installed. Are WSL and Hyper-V installed? Under my Windows Features, everything looks good, nothing out of the ordinary. &lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Windows Features dialog box where you can enable Windows Services and features.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;408&quot; height=&quot;806&quot; src=&quot;https://nestenius.se/_astro/windows-features-hyper-v-containers-wsl-enabled.Dw7EfwI2_AsYda.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;solving-the-mystery-that-blocks-docker-in-an-azure-vm&quot;&gt;Solving the mystery that blocks Docker in an Azure VM&lt;/h2&gt;
&lt;p&gt;After a long period of searching on Google and reviewing GitHub issues, I began to find indications that suggest an absence of virtualization support. It’s strange because running Docker in Azure Virtual Machine has worked fine for me in the past.&lt;/p&gt;
&lt;p&gt;But let’s confirm this by first looking at the Task Manager.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Windows Task Manager where the virtualization option is not shown&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;666&quot; height=&quot;593&quot; src=&quot;https://nestenius.se/_astro/windows-task-manager-cpu-performance-azure-vm-intel-xeon.BccvoqBn_Z1cRNtS.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Something is missing here! &lt;/p&gt;
&lt;p&gt;The Task Manager of a computer running Docker looks like this: &lt;img alt=&quot;The Windows Task Manager where virtualization is enabled&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;666&quot; height=&quot;593&quot; src=&quot;https://nestenius.se/_astro/windows-task-manager-cpu-virtualization-enabled-azure-vm.BHxtF_po_Z1bvj5Y.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-is-virtualization-disabled&quot;&gt;Why is virtualization disabled?&lt;/h2&gt;
&lt;p&gt;Apparently, the virtualization feature is not present in my Virtual Machine. Did you notice my mistake when I created the virtual machine above? No, that is easy to miss!&lt;/p&gt;
&lt;p&gt;Apparently, a new concept called &lt;strong&gt;Trusted Launch Virtual Machines&lt;/strong&gt; has been introduced. Trusted machines sound like a good idea! This is now the &lt;strong&gt;default setting&lt;/strong&gt; when creating a new virtual machine.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Azure Virtual Machine Security Type - Trusted launch virtual machine&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;672&quot; height=&quot;51&quot; src=&quot;https://nestenius.se/_astro/azure-vm-security-type-trusted-launch-setting.BZN4CFZf_sJ3Ku.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, when I created my virtual machine, I didn’t notice this new feature or think that it would be a big deal. It turns out that this feature disables virtualization inside the VM. ☹&lt;/p&gt;
&lt;h2 id=&quot;restoring-virtualization-for-docker-in-azure-vm&quot;&gt;Restoring Virtualization for Docker in Azure VM&lt;/h2&gt;
&lt;p&gt;The solution is to select the standard security type when creating a new virtual machine.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Azure Virtual Machine - Security type - Standard&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;679&quot; height=&quot;32&quot; src=&quot;https://nestenius.se/_astro/how-do-i-get-the-support-back.C_9bYCTe_Z42SRd.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now it works!&lt;/p&gt;
&lt;h2 id=&quot;annoying-dark-patterns-in-azure-portal&quot;&gt;Annoying dark patterns in Azure Portal&lt;/h2&gt;
&lt;p&gt;I find it quite frustrating that when I’m creating a new Virtual Machine in the portal, the default OS disk type is set to the more expensive &lt;strong&gt;Premium SSD&lt;/strong&gt; type. This can be costly when creating non-critical test machines in the cloud. In my opinion, the default setting should be Standard SSD, which is more sensible.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Choosing the Azure Virtual Machine OS disk size&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;683&quot; height=&quot;136&quot; src=&quot;https://nestenius.se/_astro/azure-vm-os-disk-settings-premium-ssd-127gb.XjV8O4Ig_Z2urVOB.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I once received a cloud bill of over 1000 USD because I forgot to delete a bunch of disk images. These images were all on a Premium SSD disk. ☹&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Things like this can really drive you crazy, and these small details can be really hard to find! During this frustrating incident, I spent about a day trying to figure it out. I then coined this quote:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;“Cloud development: making developers cry since the early 2000s.”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The experience of developing for the cloud can be pretty challenging. Spending days trying to achieve what seems obvious or simple can be frustrating.&lt;/p&gt;
&lt;p&gt;Do you agree with the pain of web development?&lt;/p&gt;
&lt;p&gt;(Ps, I didn’t cry 😊)&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;Deploy a container to Azure App Services using Azure CLI and user-assigned managed identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, If I missed anything or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>azure</category><category>azure</category></item><item><title>Deploy Container to Azure App Services with System-Assigned Identity</title><link>https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/</link><guid isPermaLink="true">https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/</guid><description>In this blog post, I will guide you through deploying a custom container image to Azure App Services from a private container registry using a</description><pubDate>Mon, 02 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post, I will guide you through deploying a custom container image to Azure App Services from a private container registry using a &lt;strong&gt;system-assigned managed identity&lt;/strong&gt; and the &lt;strong&gt;Azure CLI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We will focus on leveraging the &lt;strong&gt;system-assigned managed identity&lt;/strong&gt; approach in this article. For those interested in using a &lt;strong&gt;user-assigned managed identity&lt;/strong&gt;, a separate post covering that method can be found &lt;strong&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want to know more about managed identities in Azure, visit the article &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview&quot;&gt;What are managed identities for Azure resources?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;In my previous blog post, I covered &lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;deploying a container image to Azure App Services using a user-assigned managed identity&lt;/a&gt;. This blog post will show how this can be simplified using a system-assigned managed identity.&lt;/p&gt;
&lt;p&gt;This blog post will focus on the differences compared with the previous approach, so head over to &lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;that one&lt;/a&gt; first before you read this blog post.&lt;/p&gt;
&lt;h2 id=&quot;user-vs-system-assigned-identity&quot;&gt;User vs. system-assigned identity&lt;/h2&gt;
&lt;p&gt;In the world of Azure identities, it’s crucial to understand the distinction between user-assigned and system-assigned identities. Let’s explore the key differences.&lt;/p&gt;
&lt;h3 id=&quot;user-assigned-identities&quot;&gt;&lt;strong&gt;User-Assigned Identities&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In our previous post, we explored user-assigned identities. Here’s a recap of the essentials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Independent Creation:&lt;/strong&gt; The identity is created separately from the App Service resource.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible Assignment:&lt;/strong&gt; This identity can be assigned to multiple resources, providing flexibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lifecycle Management:&lt;/strong&gt; Developers are responsible for managing the identity’s lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Longevity:&lt;/strong&gt; User-assigned identities can be long-lived, making them ideal for applications that require consistent identity usage over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;system-assigned-identities&quot;&gt;&lt;strong&gt;System-assigned identities&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;These identities share similarities with user-assigned identities but with some key differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automatic Creation&lt;/strong&gt;: Azure creates the identity when the resource is created.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource-Specific&lt;/strong&gt;: The identity is tied to a specific resource.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic Deletion&lt;/strong&gt;: The identity is deleted when the resource is deleted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoped to Resource&lt;/strong&gt;: The identity is limited to the specific resource it is assigned to.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The primary advantage of system-assigned identities is the convenience that they offer. Azure manages the creation and deletion of these identities which reduces the manual effort required.&lt;/p&gt;
&lt;p&gt;Understanding these differences helps in choosing the right identity type for your Azure resources so that they can be securely and efficiently managed.&lt;/p&gt;
&lt;p&gt;Let’s explore how using system assigned identities can simplify the effort to deploy a container image to Azure App Services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I am not a regular PowerShell developer.&lt;/li&gt;
&lt;li&gt;I would like to get feedback on the script! How can I improve it? Bugs? Style? Or am I doing it wrong?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;part-1--the-settings-file&quot;&gt;Part 1 – The settings file&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Settings.ps1&lt;/strong&gt; file is almost the same as the one we used before, with the key difference being that we do not need to specify any &lt;strong&gt;identityName&lt;/strong&gt;. The script is as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# resource group name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;MyTestResourceGroup&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# location&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$location &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;swedencentral&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The name of the App Service Plans&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;asp-MyApp-Linux-dev&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The SKU of the App Service Plans&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppServicePlanSKU_Linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;S1&apos;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     # Standard plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The name of the App Services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;MyApp-Linux-Container-dev&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Azure container registry name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$ACRName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;tncontaineregistry&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# AzureRover container image name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$imagename &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;mycontainerimage&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;part-2--creating-the-azure-app-service-infrastructure&quot;&gt;Part 2 – Creating the Azure App Service infrastructure&lt;/h2&gt;
&lt;p&gt;The script &lt;strong&gt;1-Infrastructure.ps1&lt;/strong&gt; that creates the initial infrastructure can be handily reduced to the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Step &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: Create the resource group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the resource group.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$resgroup &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az group create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;location $location &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$resId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $resgroup.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Resource group created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${resId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 2: Create Azure Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the Azure Container Registry.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$acr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az acr create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $ACRName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sku Basic &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;admin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;enabled true &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$acrid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $acr.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Azure Container Registry created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${acrid}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, the big difference is that we don’t have to create any identity manually. After running the above script, you should only find one resource in your resource group:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The created Azure Registry in Azure Portal&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;564&quot; height=&quot;67&quot; src=&quot;https://nestenius.se/_astro/azure-portal-container-registry-resource-list.C8GCaM2P_2rR98J.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;part-3--pushing-the-docker-image-to-the-container&quot;&gt;Part 3 – Pushing the docker image to the container&lt;/h2&gt;
&lt;p&gt;Creating the container image locally is beyond the scope of this blog post. However, the script below (found in &lt;strong&gt;2-BuildAndPushDockerImage.ps1&lt;/strong&gt;) will log in to ACR, tag, and push the image to the ACR.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; .\Settings.ps1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 1: Log in to the Azure Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Logging into Azure Container Registry &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${ACRName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;If this step hangs, ensure Docker is running locally.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;az acr login &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $ACRName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 2 Tag the local Docker image with the registry server name of your ACR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$taggedImage &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${ACRName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.azurecr.io/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagename}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$localImageName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;mycontainerimage&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Tagging the Docker image &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${localImageName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos; with &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${taggedImage}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;docker tag $localImageName $taggedImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 3: Push the local image to Azure Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Pushing the Docker image &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${taggedImage}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos; to ACR.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;docker push $taggedImage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script above assumes you have an image named “mycontainerimage” in your local Docker environment and this script is the same as we used in the previous blog post.&lt;/p&gt;
&lt;p&gt;After you run the script, you should be able to find the image in the registry within the portal:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The resulting container image in Azure Container Registyr&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;710&quot; height=&quot;280&quot; src=&quot;https://nestenius.se/_astro/azure-container-registry-mycontainerimage-repository-tags.BIKdeqiP_2eCMEF.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;After this step, we assume that a container image is in a registry with the following URL:&lt;br&gt;
&lt;strong&gt;tncontaineregistry.azurecr.io/mycontainerimage:latest&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;part-4--creating-the-azure-app-service&quot;&gt;Part 4 – Creating the Azure App Service&lt;/h2&gt;
&lt;p&gt;This script is simpler compared to the script we used in the previous blog post. The goal of this script is to perform the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Retrieve the ID of the Azure Container Registry.&lt;/li&gt;
&lt;li&gt;Create the Linux App Service Plan.&lt;/li&gt;
&lt;li&gt;Create the container App Service.&lt;/li&gt;
&lt;li&gt;Set the identity in the App Service to access the ACR.&lt;/li&gt;
&lt;li&gt;Verify the ACR identity.&lt;/li&gt;
&lt;li&gt;Enable Application Logging (Filesystem).&lt;/li&gt;
&lt;li&gt;Enable log streaming (optional).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This script, named &lt;strong&gt;3-CreateAppService-UserAssigned.ps1&lt;/strong&gt;, is a bit bigger, so we will review it step by step in detail below.&lt;/p&gt;
&lt;h3 id=&quot;step-1--get-the-id-of-the-azure-container-registry&quot;&gt;&lt;em&gt;Step #1 – Get the ID of the Azure Container Registry&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;We first need to retrieve the ID of the registry we created earlier and we can do this by using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Querying for the container registry ID&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$acr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az acr show &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $acrName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$acrId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $acr.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ACR found with ID: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${acrId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2--create-the-linux-app-service-plan&quot;&gt;&lt;em&gt;Step #2 – Create the Linux App Service Plan&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;The next step involves creating a &lt;strong&gt;Linux-based App Service Plan&lt;/strong&gt; which is required to host and run your App Services. In this part, we don’t do anything unusual:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the Linux App Service Plan&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$servicePlan &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az appservice plan create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --is-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sku $AppServicePlanSKU_Linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$servicePlanId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $servicePlan.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;App Service Plan created with id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${servicePlanId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3---create-the-azure-container-app-service&quot;&gt;&lt;em&gt;Step #3 - Create the Azure Container App Service&lt;/em&gt; &lt;/h3&gt;
&lt;p&gt;The next step is to create the &lt;strong&gt;container-based App Service&lt;/strong&gt; to run on the previously created App Service Plan. This time, we will use a &lt;strong&gt;system-assigned identity&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$imagePath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${acrname}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.azurecr.io/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagename}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the container App Service.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;With the following image &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagePath}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppService &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az webapp create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;acr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;use-identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;plan $AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $imagePath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;identity [&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;role &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;AcrPull&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope $acrId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$hostName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $AppService.defaultHostName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$appServiceID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $AppService.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;App Service created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${appServiceID}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;what-has-changed-compared-to-the-user-assigned-identity-approach&quot;&gt;&lt;strong&gt;What has changed compared to the user-assigned identity approach?&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identity Assignment:&lt;/strong&gt; Instead of manually creating and assigning an identity, we use the &lt;strong&gt;—assign-identity [system]&lt;/strong&gt; parameter. This tells Azure to create a system-assigned identity as part of the service creation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role Specification:&lt;/strong&gt; The &lt;strong&gt;role&lt;/strong&gt; parameter is used to define the role for the identity, ensuring it has the necessary permissions to pull an image from the registry.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scope Definition:&lt;/strong&gt; The &lt;strong&gt;scope&lt;/strong&gt; parameter specifies the scope within which the role will be applied, ensuring the identity has access to the container registry.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So in short, this all means a simpler and streamlined setup that works precisely to your needs compared to the approach using user-assigned identity. Pretty cool, right?&lt;/p&gt;
&lt;h2 id=&quot;reviewing-the-azure-app-service-identity&quot;&gt;&lt;strong&gt;Reviewing the Azure App Service identity&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In the portal, Azure created a system-assigned identity for us, and we can see this under &lt;strong&gt;Settings -&gt; Identity&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;This identity was assigned the &lt;strong&gt;AcrPull&lt;/strong&gt; role:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Enabling the Azure App Service System assigned managed identity&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;402&quot; height=&quot;314&quot; src=&quot;https://nestenius.se/_astro/azure-portal-system-assigned-managed-identity-status-on.OrZmLuvf_Z2o4dY4.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;Reviewing the Azure App Service identity In the portal, Azure.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;319&quot; src=&quot;https://nestenius.se/_astro/azure-portal-role-assignment-acrpull-container-registry.Do1LyueG_Z2tVFIh.webp&quot; srcset=&quot;&quot;&gt; On the &lt;strong&gt;Deployment -&gt; Deployment Center page&lt;/strong&gt;, we see that it is using a system assigned managed identity to access the registry: &lt;img alt=&quot;Confirming the Azure App Services has a system assigned Identity&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;457&quot; height=&quot;129&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-managed-identity-system-assigned-auth.DXAJyU_w_1xX7mo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;are-we-done&quot;&gt;Are we done?&lt;/h2&gt;
&lt;p&gt;Yes, running the script will successfully deploy the container image and we no longer see the dreadful application error page as we did before! We’ve also simplified  the deployment script a bit. &lt;img alt=&quot;Azure App Services giving application error&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;615&quot; height=&quot;121&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-application-error-page.C9golVmf_Z1lgccd.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Writing these two blog posts about container deployment to Azure App Services has been both a challenging and educational journey into the intricacies of Azure and PowerShell. If you’re on the journey too, I’d encourage you to read the previous blog post &lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;Deploy containers Azure App Services using user-assigned managed identity&lt;/a&gt; alongside this one to compare these two approaches.&lt;/p&gt;
&lt;p&gt;In conclusion, using the system-assigned identity approach for App Services significantly simplifies the process. However, it would be even more streamlined if Microsoft allowed specifying the ACR identity directly in the &lt;strong&gt;az webapp create&lt;/strong&gt; command. This enhancement would further simplify the deployment process and the developer experience.&lt;/p&gt;
&lt;p&gt;You can find the complete scripts here &lt;strong&gt;&lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/&quot;&gt;Deploy a container to Azure App Services using Azure CLI and user-assigned managed identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, if I missed anything, or if you have any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>azure</category><category>azure</category></item><item><title>Deploy containers Azure App Services using user-assigned managed identity</title><link>https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/</link><guid isPermaLink="true">https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/</guid><description>This blog post describes my approach to successfully deploying a custom container image to Azure App Services from a private container registry, using a</description><pubDate>Tue, 27 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This blog post describes my approach to successfully deploying a custom container image to Azure App Services from a private container registry, using a user-assigned managed identity and the Azure CLI.&lt;/p&gt;
&lt;p&gt;This blog post will cover how to do this using a &lt;strong&gt;user-assigned managed identity&lt;/strong&gt;, and a separate post will cover how to do this using a &lt;strong&gt;&lt;a href=&quot;https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;system-assigned managed identity&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-i-did-this&quot;&gt;Why I Did This&lt;/h2&gt;
&lt;p&gt;For this project my goal was clear: I wanted to deploy a custom Linux container from Azure Container Registry and host it in Azure App Services. Although there are many hosting alternatives in Azure, my focus was on achieving this specific deployment using Azure App Services.&lt;/p&gt;
&lt;p&gt;Here’s the constraints I set:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The script should be fully scripted using &lt;strong&gt;Azure CLI&lt;/strong&gt; and &lt;strong&gt;PowerShell&lt;/strong&gt;, not Bicep or ARM templates. &lt;/li&gt;
&lt;li&gt;The container image should be hosted in a &lt;strong&gt;private repository&lt;/strong&gt; in an Azure Container Registry. &lt;/li&gt;
&lt;li&gt;I want to properly utilize a &lt;strong&gt;user-assigned&lt;/strong&gt; managed identity for my App Service to pull the image from the registry, instead of using the admin username/password approach. &lt;/li&gt;
&lt;li&gt;The script should create all the necessary resources, including the resource group, app-service plan, and more.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Out of scope for this blog post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The creation of the container image.&lt;/li&gt;
&lt;li&gt;Proper error handling.&lt;/li&gt;
&lt;li&gt;Using deployment slots or using a CI/CD system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I thought this would be easy, but boy, was I wrong! I assumed it would only take a few hours because I’ve been using Azure App Services for some time. However, this project turned out to be quite challenging. I encountered bugs, unexpected issues, confusing documentation, and frustration.&lt;/p&gt;
&lt;p&gt;Eventually, I was able to make something work, and now I’m sharing my findings with you in this blog post so you don’t have to go through the same pain and suffering.&lt;/p&gt;
&lt;p&gt;Important:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I am not a regular PowerShell developer.&lt;/li&gt;
&lt;li&gt;I would like to get feedback on the script! How can I improve it? Bugs?  Style? Or am I doing it wrong?&lt;/li&gt;
&lt;li&gt;I did all of this as part of my AZ-204 certification study.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;part-1--the-settings-file&quot;&gt;Part 1 – The Settings file&lt;/h3&gt;
&lt;p&gt;I will be utilizing multiple scripts to accomplish my objectives and all the scripts can be found &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/AzureAppServices&quot;&gt;here&lt;/a&gt;. To get started, I created a shared &lt;strong&gt;Settings.ps1&lt;/strong&gt; file that consolidates all the settings for this deployment. This script should be fairly self-explanatory and it looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# resource group name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;MyTestResourceGroup&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# location&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$location &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;swedencentral&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# My identity name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$identityName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;MyAppIdentity&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The name of the App Service Plans&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;asp-MyApp-Linux-dev&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The SKU of the App Service Plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppServicePlanSKU_Linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;S1&apos;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     # Standard plan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The name of the App Services&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;MyApp-Linux-Container-dev&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Azure container registry name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$ACRName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;tncontaineregistry&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# AzureRover container image name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$imagename &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;mycontainerimage&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;part-2--creating-the-infrastructure&quot;&gt;Part 2 – Creating the infrastructure&lt;/h2&gt;
&lt;p&gt;Next, we need to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a resource group.&lt;/li&gt;
&lt;li&gt;Create a managed identity.&lt;/li&gt;
&lt;li&gt;Create an Azure Container Registry.&lt;/li&gt;
&lt;li&gt;Assign the AcrPull role to the identity to pull images from the registry.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I will call this script “&lt;strong&gt;1-Infrastructure.ps1&lt;/strong&gt;,” and it looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Run the settings script&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; .\Settings.ps1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 1: Create the resource group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the resource group.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$resgroup &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az group create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;location $location &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$resId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $resgroup.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Resource group created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${resId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 2: Create a managed identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating a managed identity.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$identity &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az identity create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $identityName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$identityId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $identity.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$principalId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $identity.principalId&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;User-assigned managed identity created&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;name: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${identityName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${identityId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;h&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;PrincipalId: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${principalId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Waiting 30s to ensure the identity is fully registered and propagated in Azure AD...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# The AcrPull assignment might otherwise fail.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Start-Sleep&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; -&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Seconds &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 3: Create Azure Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the Azure Container Registry.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$acr &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az acr create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $ACRName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sku Basic &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;admin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;enabled true &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$acrid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $acr.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Azure Container Registry created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${acrid}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 4: Assign the AcrPull role to the managed identity on the ACR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Assigning AcrPull role to the managed identity.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$role &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az role assignment create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;assignee $principalId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;role &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;AcrPull&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope $acrid &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        |&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the script above, everything is standard, except that I needed to include a delay to ensure that the identity is registered and propagated correctly in Azure before assigning the role to it.After that, you can expect to find these two items in your resource group in the portal:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Managed Identity for the App Service&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;668&quot; height=&quot;93&quot; src=&quot;https://nestenius.se/_astro/azure-portal-managed-identity-and-container-registry-resourc.MFdl7vd__Z9N5Ms.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The MyAppIdentity identity should also have been assigned the AcrPull role:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The MyAppIdentity identity should also have been assigned the AcrPull role:&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;597&quot; height=&quot;73&quot; src=&quot;https://nestenius.se/_astro/azure-acr-pull-role-assignment-managed-identity.B533G8oC_2srhjw.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Awesome! So far so good!&lt;/p&gt;
&lt;h2 id=&quot;part-3--pushing-the-image-to-the-container&quot;&gt;Part 3 – Pushing the image to the container&lt;/h2&gt;
&lt;p&gt;Creating the container image locally is beyond the scope of this blog post. However, the script (found in &lt;strong&gt;2-BuildAndPushDockerImage.ps1&lt;/strong&gt;) to log in to ACR, tag, and push the image is below.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; .\Settings.ps1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 1: Log in to the Azure Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Logging into Azure Container Registry &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${ACRName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;If this step hangs, ensure Docker is running locally.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;az acr login &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;--&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $ACRName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 2 Tag the local Docker image with the registry server name of your ACR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$taggedImage &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${ACRName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.azurecr.io/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagename}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$localImageName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;mycontainerimage&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Tagging the Docker image &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${localImageName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos; with &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${taggedImage}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;docker tag $localImageName $taggedImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 3: Push the local image to Azure Container Registry&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Pushing the Docker image &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${taggedImage}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos; to ACR.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;docker push $taggedImage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script above assumes you have an image named &lt;strong&gt;“mycontainerimage”&lt;/strong&gt; in your local Docker environment. &lt;/p&gt;
&lt;p&gt;After you run the script, you should be able to find the image in the registry in the portal:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Display of the deployed container image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;710&quot; height=&quot;280&quot; src=&quot;https://nestenius.se/_astro/azure-container-registry-repository-mycontainerimage-latest-.PyAix1NK_28YYxh.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;After this step, we assume that a container image is in the registry with the following URL: &lt;strong&gt;tncontaineregistry.azurecr.io/mycontainerimage:latest&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;part-4--creating-the-app-service&quot;&gt;Part 4 – Creating the App Service&lt;/h2&gt;
&lt;p&gt;This part was challenging as I spent much time on trial and error. The goal of this script is to perform the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Retrieve details about the existing managed identity.&lt;/li&gt;
&lt;li&gt;Create the Linux App Service Plan.&lt;/li&gt;
&lt;li&gt;Create the container App Service.&lt;/li&gt;
&lt;li&gt;Set the identity in the App Service to access the ACR.&lt;/li&gt;
&lt;li&gt;Verify the ACR identity.&lt;/li&gt;
&lt;li&gt;Enable Application Logging (Filesystem).&lt;/li&gt;
&lt;li&gt;Enable log streaming (optional).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This script, named &lt;strong&gt;3-CreateAppService-UserAssigned.ps1&lt;/strong&gt;, is a bit bigger, so we will review it step by step in detail below.&lt;/p&gt;
&lt;h3 id=&quot;step-1--getting-the-managed-identity-details&quot;&gt;&lt;em&gt;Step #1 – Getting the Managed Identity details&lt;/em&gt;  &lt;/h3&gt;
&lt;p&gt;Firstly, we retrieve the required details about the &lt;strong&gt;managed Identity&lt;/strong&gt; we created earlier.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;#Run the settings script&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; .\Settings.ps1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 1: Get the details about the existing managed identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Retrieving the managed identity &apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${identityName}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$identity &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az identity show &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $identityName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$identityId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $identity.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$principalId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $identity.principalId&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$clientId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $identity.clientId&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Managed identity retrieved&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${identityId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;PrincipalId: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${principalId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;ClientId: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${clientId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2--create-the-linux-app-service-plan&quot;&gt;&lt;em&gt;Step #2 – Create the Linux App Service Plan&lt;/em&gt;  &lt;/h3&gt;
&lt;p&gt;The next step involves creating a Linux-based App Service Plan, which is required to host and run our App Services. In this part, we don’t do anything unusual.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the Linux App Service Plan&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$servicePlan &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az appservice plan create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --is-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sku $AppServicePlanSKU_Linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$servicePlanId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $servicePlan.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;App Service Plan created with id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${servicePlanId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3---create-the-container-app-service&quot;&gt;&lt;em&gt;Step #3 - Create the container App Service&lt;/em&gt; &lt;/h3&gt;
&lt;p&gt;The next step is to create the container-based App Service to run on the previously created App Service Plan.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$imagePath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${acrname}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.azurecr.io/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagename}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the container App Service.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;With the following image &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagePath}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppService &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az webapp create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;acr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;use-identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;plan $AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $imagePath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;identity $identityId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$hostName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $AppService.defaultHostName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$appServiceID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $AppService.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;App Service created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${appServiceID}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we do a few important things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We set the &lt;strong&gt;acr-use-identity&lt;/strong&gt; flag to indicate that we want to pull the image from Azure Container Registry using a managed identity.&lt;/li&gt;
&lt;li&gt;We assign our managed identity to the service using the &lt;strong&gt;assign-identity&lt;/strong&gt; flag.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It might seem like these steps are all it takes to create a new App Service and have it pull the image from our container registry. &lt;strong&gt;But I was so wrong here!&lt;/strong&gt; &lt;strong&gt;Now we are getting into the ugly parts&lt;/strong&gt;☹If you run the commands above, then it will fail with this error: &lt;img alt=&quot;Azure App Services giving application error&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;615&quot; height=&quot;121&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-application-error-page.DN-ZpeZ9_PuDeh.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the logs, you may encounter various &lt;strong&gt;image pull failures&lt;/strong&gt;, such as the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;WARN  - Image pull failed. Defaulting to local copy if present.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ERROR - Image pull failed: Verify docker image configuration and credentials (if using private repository)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;INFO  - Stopping site myapp-linux-container-dev because it failed during startup.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ERROR - DockerApiException: Docker API responded with status code=InternalServerError,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  response={&quot;message&quot;:&quot;Head \&quot;https://tncontaineregistry.azurecr.io/v2/mycontainerimage/manifests/latest\&quot;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  unauthorized: authentication required, visit https://aka.ms/acr/authorization for more information.&quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-azure-app-service-setup-so-far&quot;&gt;The Azure App Service setup so far&lt;/h2&gt;
&lt;p&gt;So far, we have this setup:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Azure App Service and Container Registry&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;214&quot; src=&quot;https://nestenius.se/_astro/app-service-user-assigned-managed-identity-acr-pull-diagram.DaKtoFRc_Z1CT1Fh.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The service has a &lt;strong&gt;user-assigned managed identity&lt;/strong&gt; (found under &lt;strong&gt;Settings-&gt;Identity)&lt;/strong&gt;. This means we created the user and then assigned that user to the app service. The user’s lifetime is independent of the app service itself, and the identity might be associated with multiple Azure services.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Azure App Service - User Assigned Managed Identity&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;245&quot; height=&quot;235&quot; src=&quot;https://nestenius.se/_astro/Azure-App-Services-Image-unnamed.CKKlSANn_1LqP3j.webp&quot; srcset=&quot;&quot;&gt; So this is good!&lt;/p&gt;
&lt;p&gt;Under &lt;strong&gt;Deployment -&gt; Deployment Center&lt;/strong&gt;, we see the following:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Under Deployment -&amp;gt; Deployment Center, we see the following:&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;466&quot; height=&quot;166&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-managed-identity-registry-auth-settings.CusxowfW_Z6KetV.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, it correctly understands that we want to use &lt;strong&gt;managed identity&lt;/strong&gt;, which is good! But the identity is wrong! The identity here should be set to this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Managed Identity to access the Container Registry is wrong!&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;440&quot; height=&quot;123&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-managed-identity-authentication-selected.CYsuQyNS_19h5QD.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Wait, what? Do we have two different identities in our App Service? &lt;/p&gt;
&lt;p&gt;Yes, the hidden truth is that we have two different identities to deal with and we need to specify both identities to get this to work as the image below shows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Showing that an App Service have two different identites&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;766&quot; height=&quot;291&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-managed-identity-pull-from-acr-diagram.Dy-yZGSN_1TWoO.webp&quot; srcset=&quot;&quot;&gt; The frustrating part is that we cannot specify the &lt;strong&gt;ACR identity&lt;/strong&gt; in the &lt;strong&gt;az webapp create&lt;/strong&gt; command we used earlier. If we could do that, it would have prevented a lot of pain and suffering, which we will explore later in this blog post (feel free to correct me if I am wrong here).&lt;/p&gt;
&lt;h3 id=&quot;step-4--setting-the-acr-identity&quot;&gt;Step #4 – Setting the ACR identity  &lt;/h3&gt;
&lt;p&gt;Here comes the most challenging part: How do we set this second identity using Azure CLI? There are various methods to accomplish this.&lt;/p&gt;
&lt;p&gt;If you consult the documentation, you may come across several solutions, such as this one:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;az&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; resource&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --ids&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $Webapp_Config &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;--set&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; properties.AcrUserManagedIdentityID=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$ClientID&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or this one &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/app-service/configure-custom-container?tabs=debian&amp;#x26;pivots=container-linux&quot;&gt;here&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;az&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; webapp&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; config&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --resource-group&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;group-nam&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;app-nam&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; --generic-configurations&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;{&quot;acrUserManagedIdentityID&quot;: &quot;&amp;#x3C;client-id&gt;&quot;}&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neither will, however, work! What is the solution?&lt;/p&gt;
&lt;p&gt;For the first example, the location of the setting seems to have changed. A working solution is:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$property &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;properties.siteConfig.AcrUserManagedIdentityID=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${ClientID}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$tmp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az resource update &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ids $appServiceID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;set $property &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And using JSON in the second example, you can fix it by doing it like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;{\&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;acrUserManagedIdentityID\&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;: \&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${clientId}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;\&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$tmp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az webapp config set &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;generic&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;configurations $data &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution here is that you need to &lt;strong&gt;double-encode&lt;/strong&gt; the JSON due to an issue in PowerShell. You can find more information about it &lt;a href=&quot;https://github.com/Azure/azure-cli/blob/dev/doc/quoting-issues-with-powershell.md&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;https://github.com/Azure/azure-cli/issues/8506&quot;&gt;here&lt;/a&gt;, and &lt;a href=&quot;https://github.com/Azure/azure-cli/issues/10248&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;step-5--verify-the-acr-identity&quot;&gt;Step #5 – Verify the ACR identity &lt;/h3&gt;
&lt;p&gt;To ensure that it is setup correctly, we can query the App Service configuration using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$settings &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az webapp config show &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;These two settings must be set for successful ACR pull:&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;acrUseManagedIdentityCreds=&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$settings.acrUseManagedIdentityCreds&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;acrUserManagedIdentityID=&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$settings.acrUserManagedIdentityId&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;acrUseManagedIdentityCreds&lt;/strong&gt; should be set to &lt;strong&gt;true&lt;/strong&gt; and the &lt;strong&gt;acrUserManagedIdentityID&lt;/strong&gt; should contain the &lt;strong&gt;GUID&lt;/strong&gt; for your managed identity.&lt;/p&gt;
&lt;p&gt;You can also verify this in the portal under &lt;strong&gt;App Service&lt;/strong&gt; -&gt; &lt;strong&gt;Deployment- &gt; Deployment center&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The Azure App Service Registry settings&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;442&quot; height=&quot;343&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-registry-settings-managed-identity-selecte.Cm56YVHq_i1ozU.webp&quot; srcset=&quot;&quot;&gt; Great, now the ACR identity has been successfully set!&lt;/p&gt;
&lt;h3 id=&quot;step-6--enable-application-logging-filesystem&quot;&gt;Step #6 – Enable Application Logging (Filesystem)  &lt;/h3&gt;
&lt;p&gt;To help with debugging our container, we can enable both the application and container logs to be sent to the filesystem using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$tmp &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az webapp log config &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;application&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;logging filesystem &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;logging filesystem &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;level verbose &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;      --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;*Please note that these settings may need to be adjusted when running in a production environment.*&lt;/p&gt;
&lt;h3 id=&quot;step-7--enable-log-streaming-optional&quot;&gt;Step #7 – Enable log streaming (optional)&lt;/h3&gt;
&lt;p&gt;Adding the following command at the end of an Azure deployment script enables real-time monitoring of application logs to start after the script is completed. This allows for immediate detection and troubleshooting of any issues during deployment. This is useful for local debugging!&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Optionally enable this to run log stream for debugging purposes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;az webapp log tail &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Are we done?&lt;/p&gt;
&lt;p&gt;If you run the above script, your container should eventually appear! Great! It works! However, can we improve it?&lt;/p&gt;
&lt;p&gt;The current developer experience is not good, because the current experience for the first deployment is like this: &lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The less optimal Azure App Service startup experience&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1286&quot; height=&quot;1071&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-startup-application-error-then-success.C5R-yBRm_Z1vnXpi.webp&quot; srcset=&quot;&quot;&gt; Upon attempting to access your new deployment as a developer, you will initially encounter the application error page. This page will remain visible until the container is successfully pulled from the registry, which could take 2-5 minutes.&lt;/p&gt;
&lt;p&gt;This lack of visibility creates uncertainties such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the deployment broken?&lt;/li&gt;
&lt;li&gt;Is my application broken?&lt;/li&gt;
&lt;li&gt;Is something else broken?&lt;/li&gt;
&lt;li&gt;Or is it simply busy with the deployment process?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How can we enhance this experience?&lt;/p&gt;
&lt;h2 id=&quot;improving-the-script&quot;&gt;Improving the script&lt;/h2&gt;
&lt;p&gt;One option that I came up with is to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First deploy the built-in .NET Core 8 runtime instead of my container.&lt;/li&gt;
&lt;li&gt;Set the ACR Identity as we did above.&lt;/li&gt;
&lt;li&gt;Replace the runtime with my custom container image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By doing this change, we will get this user experience instead: &lt;img alt=&quot;A better Azure App Service startup experience&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;957&quot; height=&quot;1059&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-container-deployment-web-app-running.B2JIYdWF_2n2fTt.webp&quot; srcset=&quot;&quot;&gt; This user experience is much more user and developer-friendly than the previous approach. The script that implements this is named &lt;strong&gt;4-CreateAppService-UserAssigned-version2.ps1&lt;/strong&gt; and it contains the following two changes:First, we create the App Service using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Step 3: Create the App Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Creating the App Service with the default runtime &apos;DOTNETCORE:8.0&apos;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$AppService &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; az webapp create &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;acr&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;use-identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;plan $AppServicePlan_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;runtime &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;DOTNETCORE:8.0&apos;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; `&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;assign&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;identity $identityId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$hostName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $AppService.defaultHostName&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$appServiceID &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; $AppService.id&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;App Service created, id: &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${appServiceID}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key difference is that we replaced &lt;strong&gt;—container-image-name&lt;/strong&gt; with &lt;strong&gt;—runtime ‘DOTNETCORE:8.0’&lt;/strong&gt;. This will start up the App Service with a more friendly welcome message.  Then, at the end of the script, after we have properly set up the ACR identity, we switch to use the container image instead by using this command:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;powershell&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;$imagePath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${acrname}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.azurecr.io/&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagename}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;:latest&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Write-Host&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;`n`n&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;Change the service to use the container &lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;${imagePath}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;az webapp config container set &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $AppServiceName_container_linux &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;resource&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;group $rgname &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;container&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;image&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;name $imagePath &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;  --&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;output json &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; ConvertFrom-Json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only drawback is that the total deployment time will be slightly longer.&lt;/p&gt;
&lt;h4 id=&quot;tips-and-tricks&quot;&gt;Tips and tricks&lt;/h4&gt;
&lt;p&gt;In your logs, you might find the &lt;strong&gt;Kudulite&lt;/strong&gt; logo displayed when the service starts up, like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Displaying the appliation logo in the logs at startup is a good idea!&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;650&quot; height=&quot;216&quot; src=&quot;https://nestenius.se/_astro/azure-app-service-linux-kudu-startup-logs.D1g2cJC9_Z1hOuTj.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;How can you do that yourself?&lt;/p&gt;
&lt;p&gt;To generate a logo, visit this &lt;a href=&quot;https://patorjk.com/software/taag&quot;&gt;site&lt;/a&gt; and create your own logo. Then I simply print it out to the console in start startup, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;AzureRover starting up&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;       _____                             __________                          &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;      /  _  \ __________ _________   ____\______   \ _______  __ ___________ &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;     /  /_\  \\___   /  |  \_  __ \_/ __ \|       _//  _ \  \/ // __ \_  __ \&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;    /    |    \/    /|  |  /|  | \/\  ___/|    |   (  &amp;#x3C;_&gt; )   /\  ___/|  | \/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;    \____|__  /_____ \____/ |__|    \___  &gt;____|_  /\____/ \_/  \___  &gt;__|   &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;            \/      \/                  \/       \/                 \/       &quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Information&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will show up in the log like this: &lt;img alt=&quot;Startup banner / logo for the Azure cloud debugger&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;866&quot; height=&quot;141&quot; src=&quot;https://nestenius.se/_astro/azurerover-container-startup-logs-app-service.DcvnmbH-_Z1nbRqv.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Isn’t this a useful thing to have? Don’t you agree? (AzureRover is just the name of an internal project)&lt;/p&gt;
&lt;h2 id=&quot;summary-and-the-scripts&quot;&gt;Summary and the scripts&lt;/h2&gt;
&lt;p&gt;Getting this to work was a painful experience and hopefully, this post can help others to have a much smoother process.The complete scripts can be found &lt;strong&gt;&lt;a href=&quot;https://github.com/tndataab/PublicBlogContent&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A separate blog post will cover how this can be slightly simplified using a system-assigned managed identity.&lt;/p&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/identityserver-in-production/&quot;&gt;IdentityServer in Production&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, if I missed anything, or if you have any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>azure</category><category>azure</category></item><item><title>Discovering .NET codebases using code coverage and NCrunch</title><link>https://nestenius.se/net/discovering-net-codebases-using-code-coverage-and-ncrunch/</link><guid isPermaLink="true">https://nestenius.se/net/discovering-net-codebases-using-code-coverage-and-ncrunch/</guid><description>Exploring and discovering unfamiliar codebases is always a challenge. In this blog post, I will introduce a novel way to explore a new codebase by looking</description><pubDate>Thu, 04 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Exploring and discovering unfamiliar codebases is always a challenge. In this blog post, I will introduce a novel way to explore a new codebase by looking at the code coverage using NCrunch.NET.&lt;/p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;As a developer, you’re often challenged to understand new libraries; you ask: ‘how does it work’, ‘what makes it tick?’, what parts are involved in this operation?’&lt;/p&gt;
&lt;p&gt;I’ve found that usually just reading the documentation or doing tutorials isn’t enough to really grasp how a given code or library works. The goal is actually to get a good &lt;strong&gt;mental model&lt;/strong&gt; for how a codebase works. &lt;/p&gt;
&lt;p&gt;This is especially true when I create training material and teaching concepts for my training classes at &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/&quot;&gt;tn-data.se&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;example-the-library-to-understand&quot;&gt;Example: The library to understand&lt;/h2&gt;
&lt;p&gt;In my case, I wanted to understand how the &lt;a href=&quot;https://github.com/AzureAD/microsoft-authentication-library-for-dotnet&quot;&gt;&lt;strong&gt;Microsoft Authentication Library&lt;/strong&gt;&lt;/a&gt; works under the hood. We can use this library to acquire access tokens from Azure Entra ID using OpenID-Connect.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Microsoft Authentication Library for .NET&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1053&quot; height=&quot;257&quot; src=&quot;https://nestenius.se/_astro/msal-net-token-flow-diagram-entra-id.CslBj7XO_ZM6htA.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The most obvious option is to step through the code using the debugger, which always works. But what are the alternatives? Can they be faster and more insightful?&lt;/p&gt;
&lt;p&gt;The first step I usually take is to download the library’s source code and create my own “class library” based on it. This allows me to step through the code, explore it, and introduce debug statements more easily. In my case, I did this by downloading the code from &lt;a href=&quot;https://github.com/AzureAD/microsoft-authentication-library-for-dotnet&quot;&gt;https://github.com/AzureAD/microsoft-authentication-library-for-dotnet&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I find having the source code locally is really helpful because I can easily step through the code and add debug statements or minor modifications to improve my understanding of the codebase. I used this technique when I wrote the &lt;a href=&quot;https://nestenius.se/azure/default-azure-credentials-under-the-hood/&quot;&gt;DefaultAzureCredentials Under the Hood blog&lt;/a&gt; post.&lt;/p&gt;
&lt;h2 id=&quot;exploring-the-code-base&quot;&gt;Exploring the code base&lt;/h2&gt;
&lt;p&gt;After creating the class library locally, the next step is analyzing its structure. Where should I start?&lt;/p&gt;
&lt;p&gt;An obvious starting point for exploring an existing code base is to use &lt;a href=&quot;https://www.ndepend.com&quot;&gt;&lt;strong&gt;NDepend&lt;/strong&gt;&lt;/a&gt; . &lt;a href=&quot;https://www.ndepend.com&quot;&gt;&lt;strong&gt;NDepend&lt;/strong&gt;&lt;/a&gt; is an incredible static analysis tool that helps monitor and improve the quality of .NET code through in-depth reporting, code metrics, and visualizations.&lt;/p&gt;
&lt;p&gt;The diagram below shows the complexity of the library that we are currently investigating.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of a complex codebase&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1538&quot; height=&quot;941&quot; src=&quot;https://nestenius.se/_astro/ncrunch-dependency-graph-microsoft-identity-client-namespace.BXTVPvnR_Z11UDJD.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using &lt;a href=&quot;https://www.ndepend.com&quot;&gt;&lt;strong&gt;NDepend&lt;/strong&gt;&lt;/a&gt;, I can quickly drill down into the structure of this code library and analyze its dependencies, structure, and more. This provides a static view of the codebase that makes understanding complex and unfamiliar systems much easier. &lt;/p&gt;
&lt;p&gt;This is a good starting point, but what else can I do and how can code coverage help me to understand a codebase?&lt;/p&gt;
&lt;h2 id=&quot;introducing-ncrunch---the-live-testing-tool-for-net&quot;&gt;Introducing NCrunch - the Live Testing Tool for .NET&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ncrunch.net/&quot;&gt;&lt;strong&gt;NCrunch&lt;/strong&gt;&lt;/a&gt; is an automated continuous testing tool for .NET that integrates with Visual Studio and JetBrains Rider. It provides real-time code quality and performance feedback by running tests concurrently as you type. It tracks code coverage, collects performance metrics, and displays exceptions inline, making test-driven development fun!&lt;/p&gt;
&lt;p&gt;But wait a minute, this is a code-coverage tool. How can this tool help me understand a codebase better?&lt;/p&gt;
&lt;p&gt;Now typically, you run all your tests against your application and track the code coverage using NCrunch or similar tools.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of the result of running a code coverage check&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1062&quot; height=&quot;621&quot; src=&quot;https://nestenius.se/_astro/unit-tests-coverage-class-dependency-diagram.C1S8p1Xh_2aKU5M.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;From these runs, you can get the code coverage metrics per project, for example:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Code Coverage Metrics&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;664&quot; height=&quot;208&quot; src=&quot;https://nestenius.se/_astro/ncrunch-metrics-code-coverage-solution-overview.ChITFcfF_1CiAUN.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is all good, but I don’t want the total code coverage when exploring and understanding a new codebase; I want something else!&lt;/p&gt;
&lt;p&gt;Can I use &lt;a href=&quot;https://www.ncrunch.net/&quot;&gt;&lt;strong&gt;NCrunch&lt;/strong&gt;&lt;/a&gt; in some other way to help me discover a new codebase?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What if I only have one single test? What would that enable?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;using-ncrunch-with-just-one-test&quot;&gt;Using NCrunch with just one test&lt;/h2&gt;
&lt;p&gt;Code coverage with just one test might seem ridiculous, but let’s explore this scenario.&lt;/p&gt;
&lt;p&gt;Having just one test would allow me to see the entire code path through the application for a specific operation like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Code Coverage using only one test&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1045&quot; height=&quot;571&quot; src=&quot;https://nestenius.se/_astro/unit-test-class-dependency-tree-diagram.BB_bLMlo_G5lTG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;But wait a minute? A code coverage tool only returns me the coverage numbers. Like this?&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Code Coverage Metrics&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;664&quot; height=&quot;208&quot; src=&quot;https://nestenius.se/_astro/ncrunch-metrics-code-coverage-solution-overview.ChITFcfF_1CiAUN.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;How would this help me to understand a codebase?&lt;/p&gt;
&lt;p&gt;The advantage of &lt;strong&gt;&lt;a href=&quot;https://www.ncrunch.net/&quot;&gt;NCrunch&lt;/a&gt;&lt;/strong&gt; is that it tracks my code coverage in real-time and visually shows the coverage in my editor, like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How NCrunch.net displays code coverage in the editor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1210&quot; height=&quot;636&quot; src=&quot;https://nestenius.se/_astro/ncrunch-code-coverage-dots-visual-studio-resolve-authority.Cvon3CPz_Z2h0YcQ.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;simple-code-coverage-example&quot;&gt;Simple code coverage example&lt;/h2&gt;
&lt;p&gt;Let’s look at a simple example. Let’s say we have this calculator:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Calculator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MagicAdd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; x&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; y&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; x &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; y;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;            PrintAnswer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PrintAnswer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;Positive: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;Negative: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s add a single simple test:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UnitTest1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    [&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fact&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Test1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sut&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Calculator&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        int&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; sut.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MagicAdd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;20&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we enable &lt;a href=&quot;https://www.ncrunch.net/&quot;&gt;&lt;strong&gt;NCrunch&lt;/strong&gt;&lt;/a&gt; on this project, we will see the following code coverage:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Second NCrunch code coverage example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;725&quot; height=&quot;683&quot; src=&quot;https://nestenius.se/_astro/ncrunch-code-coverage-dots-csharp-calculator-class.Dhjl9RtR_HYIAs.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using the green and black dots, I can see which part of the codebase was executed when I did this operation.&lt;/p&gt;
&lt;p&gt;Ok, this is so cool! But can we do even better?&lt;/p&gt;
&lt;h2 id=&quot;runtime-data-inspection-rdi&quot;&gt;Runtime Data Inspection (RDI)&lt;/h2&gt;
&lt;p&gt;The Runtime Data Inspection (‘RDI’) is a real-time analysis system in NCrunch that brings runtime data values and detailed code control flow inline into the IDE. You can read more about this feature &lt;strong&gt;&lt;a href=&quot;https://app.ncrunch.net/documentation/guides_runtime-data-inspection&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;If we enable this feature in our simple example, we get this result:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Runtime Data Inspection (RDI) in NCrunch&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;379&quot; height=&quot;295&quot; src=&quot;https://nestenius.se/_astro/ncrunch-code-coverage-dots-calculator-method.kxuuhDdV_Z1RScYq.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When enabled, I can get a visual representation of the code execution while editing my code.&lt;/p&gt;
&lt;p&gt;Clicking on the green marker above a given variable allows me to view its value, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Second Runtime Data Inspection (RDI) example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;365&quot; height=&quot;173&quot; src=&quot;https://nestenius.se/_astro/ncrunch-code-coverage-inline-value-display.DTXm1KTU_iHFKW.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Hotspots are marked in the left column, and the execution of a given statement is displayed, allowing you to pinpoint bottlenecks in your application.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Runtime Data Inspection (RDI) can display the execution time&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;363&quot; height=&quot;175&quot; src=&quot;https://nestenius.se/_astro/ncrunch-execution-time-tooltip-thread-sleep.BWa9QmZw_Z17lqkH.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can read more about the Runtime Data Inspection (RDI) features &lt;a href=&quot;https://app.ncrunch.net/documentation/guides_runtime-data-inspection&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;back-to-msalnet&quot;&gt;Back to MSAL.NET&lt;/h2&gt;
&lt;p&gt;So back to my example with &lt;strong&gt;&lt;a href=&quot;https://github.com/AzureAD/microsoft-authentication-library-for-dotnet&quot;&gt;MSAL.NET.&lt;/a&gt;&lt;/strong&gt; Using this technique, I can write a unit test for one specific usage of this library like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Fact&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyTest&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; tenantID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;xxxxxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; clientID&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;xxxxxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; secret&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;xxxxxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; apiUrl&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://graph.microsoft.com/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; authority&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;https://login.microsoftonline.com/&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;tenantID&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ConfidentialClientApplicationBuilder.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(clientID)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithClientSecret&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(secret)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WithAuthority&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(authority)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; scopes&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;apiUrl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.default&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AcquireTokenForClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(scopes).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ExecuteAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; accessToken&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; result.AccessToken;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, I can enable the RDI overlay and easily navigate the codebase without running the debugger!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Showing the NCrunch Overlay&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;679&quot; height=&quot;156&quot; src=&quot;https://nestenius.se/_astro/ncrunch-coverage-indicators-msal-acquire-token-method.CdDb_iMJ_Z1dqpYE.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When visualizing the content of a given variable, &lt;a href=&quot;https://www.ncrunch.net/&quot;&gt;&lt;strong&gt;NCrunch&lt;/strong&gt;&lt;/a&gt; will call &lt;strong&gt;ToString&lt;/strong&gt; on that given type.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Runtime Data Inspection (RDI) can display the content of the variables during editing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1491&quot; height=&quot;494&quot; src=&quot;https://nestenius.se/_astro/ncrunch-data-point-values-msal-acquiretoken-parameters.Dzj7AKmn_121Wom.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To see what is inside the variable, you can change the type to a &lt;strong&gt;record&lt;/strong&gt; type or manually override &lt;strong&gt;ToString&lt;/strong&gt;. When overriding &lt;strong&gt;ToString&lt;/strong&gt;, it can look like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Showing the NCrunch overlay RDI tooltip&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;522&quot; height=&quot;157&quot; src=&quot;https://nestenius.se/_astro/ncrunch-code-coverage-tooltip-correlation-id-details.5CxefCVA_WJeIT.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Further improvements can be made by tweaking the RDI data limits. You can read more about this &lt;strong&gt;&lt;a href=&quot;https://www.ncrunch.net/documentation/concepts_rdi-data-limits&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;In all, using NCrunch for code coverage and real-time data inspection simplifies understanding new codebases. With this tool, developers can see code execution and inspect data as they write, making learning and understanding the codebase easier. This method is efficient and makes exploring complex codebases more accessible and engaging.&lt;/p&gt;
&lt;p&gt;NDepend also works well with NCrunch; for example, you can import NCrunch code coverage data into NDepend for further analysis. Examples of what you can extract from this information can be found &lt;a href=&quot;https://www.ndepend.com/Sample-Reports/OnOrchadCore/NDependReport.html#issues-summary&quot;&gt;here&lt;/a&gt;: , &lt;a href=&quot;https://www.ndepend.com/Sample-Reports/OnOrchadCore/NDependReportFiles/src/ContentDefinitionManager.cs.html&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://www.ndepend.com/Sample-Reports/OnOrchadCore/NDependReport.html#projects-summary&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These are two tools that I highly recommend every developer should have in their toolbox.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ncrunch.net/&quot;&gt;NCrunch.NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.ndepend.com/&quot;&gt;NDepend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Keys Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/test-driven-development-in-dotnet/&quot;&gt;Test-Driven Development in .NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/csharp-beyond-the-fundamentals/&quot;&gt;C– Beyond the Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here.&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category></item><item><title>DefaultAzureCredentials Under the Hood</title><link>https://nestenius.se/azure/default-azure-credentials-under-the-hood/</link><guid isPermaLink="true">https://nestenius.se/azure/default-azure-credentials-under-the-hood/</guid><description>The DefaultAzureCredentials is key for using Azure services, but how exactly does it work and when should you use it? In this post, we’ll break down how</description><pubDate>Thu, 18 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;strong&gt;DefaultAzureCredentials&lt;/strong&gt; is key for using Azure services, but how exactly does it work and when should you use it? In this post, we’ll break down how it operates, its challenges, and other ways to access Azure services. This guide will help you get a clearer picture of how to handle Azure authentication simply and effectively.&lt;/p&gt;
&lt;h2 id=&quot;authenticating-to-azure&quot;&gt;Authenticating to Azure&lt;/h2&gt;
&lt;p&gt;When an application wants to access resources in Azure such as storage or databases, it needs to authenticate itself. To do this, the application must obtain an &lt;strong&gt;access token&lt;/strong&gt; from &lt;strong&gt;Entra ID&lt;/strong&gt;. This token allows the application to securely access resources in Azure, such as your Azure Storage Account.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How an application aquires an access token from Entra ID&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;462&quot; src=&quot;https://nestenius.se/_astro/app-entra-id-access-token-azure-storage-account-flow.nKYQy6tk_ZAaObz.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Alternative ways to access resources exist, such as using Shared Access Signature (SAS) tokens, but that is beyond the scope of this blog post.&lt;/p&gt;
&lt;p&gt;For example, to access Azure BlobStorage, a TokenCredential is required during client creation. This ensures secure access by authorized users. Here’s an example of that:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenCredential&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; credentials&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ??&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;HowToCreate&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://myblogstorage.blob.core.windows.net&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),                                    credentials);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-does-the-tokencredential-class-represent&quot;&gt;What does the TokenCredential class represent?&lt;/h2&gt;
&lt;p&gt;The TokenCredentials type in C# is an abstract base class that contains two methods to request an access token:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// Represents a credential capable of providing an OAuth token.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; abstract&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TokenCredential&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Gets an Azure.Core.AccessToken for the specified set of scopes.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;returns&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;A valid Azure.Core.AccessToken.&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;returns&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; abstract&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ValueTask&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AccessToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetTokenAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenRequestContext&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; requestContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                                         CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; abstract&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AccessToken&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GetToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenRequestContext&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; requestContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Microsoft provides many types that implement this abstract class, including:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AuthorizationCodeCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tenantId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         clientId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         clientSecret&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         authorizationCode&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzureCliCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzureDeveloperCliCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c4&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzurePowerShellCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c5&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChainedTokenCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c6&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ClientAssertionCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tenantId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                       clientId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                       assertionCallback&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c7&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ClientCertificateCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tenantId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         clientId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         clientCertificatePath&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c8&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ClientSecretCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tenantId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                    clientId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                    clientSecret&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c9&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c10&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DeviceCodeCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c11&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; EnvironmentCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c12&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; InteractiveBrowserCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c13&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ManagedIdentityCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c14&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; OnBehalfOfCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;tenantId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                   clientId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                   clientSecret&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                   userAssertion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c15&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SharedTokenCacheCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c16&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; UsernamePasswordCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;username&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         password&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         tenantId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                         clientId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c17&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCodeCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c18&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; c19&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; WorkloadIdentityCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-do-all-these-tokencredentials-do&quot;&gt;What do all these TokenCredentials do?&lt;/h2&gt;
&lt;p&gt;Each TokenCredential type employs its own approach to acquiring the access token needed to access services in Azure. Whether searching for credentials in your system or launching a separate program, the key aim is to obtain this token safely and effectively, ensuring smooth and reliable access to Azure.&lt;/p&gt;
&lt;p&gt;For example, the &lt;strong&gt;EnvironmentCredential&lt;/strong&gt; type will look for credentials in one of these sets of environment variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ClientSecret based&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;AZURE_TENANT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_SECRET&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UserName/Password based&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;AZURE_TENANT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_ID&lt;/li&gt;
&lt;li&gt;AZURE_USERNAME&lt;/li&gt;
&lt;li&gt;AZURE_PASSWORD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Certificate based&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;AZURE_TENANT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_CERTIFICATE_PATH&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_CERTIFICATE_PASSWORD&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then it will use these credentials to try to acquire an access token from Entra ID.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VisualStudioCredential&lt;/strong&gt; is another type that attempts to launch &lt;strong&gt;Microsoft.Asal.TokenService.exe&lt;/strong&gt;, which is an application located in this folder:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bat&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\AsalTokenService&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is an executable that manages token-based authentication for Microsoft services. It’s essential for enabling secure user access to Microsoft Azure and Office 365, and it is also utilized by Visual Studio to authenticate users with Microsoft accounts. This component facilitates secure single sign-on and integrates with Entra ID.&lt;/p&gt;
&lt;p&gt;This means that the &lt;strong&gt;BlobServiceClient&lt;/strong&gt; shown before accepts any of the above credentials. For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; blobUri&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://myblogstorage.blob.core.windows.net&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(blobUri, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzureCliCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(blobUri, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzureDeveloperCliCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client3&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(blobUri, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; EnvironmentCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client4&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(blobUri, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; WorkloadIdentityCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;tokencredentials-in-practice&quot;&gt;TokenCredentials in practice&lt;/h2&gt;
&lt;p&gt;We have a problem: depending on where our app executes, we need to use different TokenCredentials, depending on where it runs. This means that our code might look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenCredential&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; credentials&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;switch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (environment)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;dev-visualstudio&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        credentials &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;dev-visualstudiocode&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        credentials &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCodeCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    case&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;production&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        credentials &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ManagedIdentityCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    default&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        credentials &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzurePowerShellCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        break&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but it’s hard to maintain; especially if we want to add more options in the future, like using &lt;strong&gt;Azure CLI&lt;/strong&gt; or &lt;strong&gt;WorkloadIdentity&lt;/strong&gt; with Kubernetes. We need a simpler approach that can work across different environments.&lt;/p&gt;
&lt;h2 id=&quot;introducing-defaultazurecredential&quot;&gt;Introducing DefaultAzureCredential&lt;/h2&gt;
&lt;p&gt;To solve the above problem, Microsoft introduced the &lt;strong&gt;DefaultAzureCredential&lt;/strong&gt; type.&lt;br&gt;
How does it work?&lt;/p&gt;
&lt;p&gt;It contains a predefined list of &lt;strong&gt;TokenCredential&lt;/strong&gt; types, which it calls in a specific sequence. DefaultAzureCredential systematically checks each one on this list until it successfully finds a matching set of credentials in your environment, as shown in the following picture:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The TokenCredentials inside DefaultAzureCredentials and the execution order.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;735&quot; height=&quot;713&quot; src=&quot;https://nestenius.se/_astro/default-azure-credential-chain-managed-identity-sources.CYWQ-oTz_Z1nRHdF.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;By using this credential type instead of the others, you don’t need to worry about having to specify what credentials to use, as shown in this example below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;             new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://myblogstorage.blob.core.windows.net&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;             new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code will automatically work well in most environments because it tries to locate a valid set of credentials and then retrieve a valid access token.&lt;/p&gt;
&lt;h2 id=&quot;what-happens-if-defaultazurecredentials-cant-find-any-credentials&quot;&gt;What happens if DefaultAzureCredentials can’t find any credentials?&lt;/h2&gt;
&lt;p&gt;The first thing you notice is that it is slow! It will take &lt;strong&gt;3-8 seconds&lt;/strong&gt; to execute all the TokenCredentials on a machine without credentials.&lt;/p&gt;
&lt;p&gt;An exception is thrown when it can’t find any valid credentials, and you can see the details in the debugger foreach failed attempt:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The exception if DefaultAzureCredentials fails to get an token&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;658&quot; height=&quot;151&quot; src=&quot;https://nestenius.se/_astro/defaultazurecredential-inner-exceptions-debugger-list.kbIeHp6U_Z1HChd3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;There is a built-in option to disable specific TokenCredentials, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredentialOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ExcludeAzureCliCredential &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ExcludeVisualStudioCodeCredential &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; cred&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(options);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;caching-of-the-access-token-in-defaultazurecredentials&quot;&gt;Caching of the access token in DefaultAzureCredentials&lt;/h2&gt;
&lt;p&gt;It’s best to create a single instance of DefaultAzureCredential and reuse it throughout your application. This approach is more efficient because it leverages the credential’s internal token cache, reducing the need for repeated authentication calls.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The token cache inside a token credential&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;448&quot; height=&quot;267&quot; src=&quot;https://nestenius.se/_astro/azure-tokencredential-accesstoken-cache-diagram.Cl0SEN4d_1e0ksC.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Create a shared DefaultAzureCredential instance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; sharedCredential&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Use this shared instance wherever needed&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client1&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; BlobServiceClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://example.blob.core.windows.net&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sharedCredential);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; client2&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SecretClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://example.vault.azure.net&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    sharedCredential);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;…&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-does-the-access-token-look-like&quot;&gt;What does the access token look like?&lt;/h2&gt;
&lt;p&gt;A sample access token can look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://storage.azure.com/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://sts.windows.net/567d82a1-7f61-4da2-b955-xxxxxxxxxxxx/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;iat&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1712047403&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;nbf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1712047403&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;exp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1712051303&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;aio&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;E2NgYKh17fZg9vvfwCmWxW+7xxxxxx==&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;appid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;9bfc7d58-9841-415c-9b60-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;appidacr&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;idp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://sts.windows.net/567d82a1-7f61-4da2-b955-xxxxxxxxxxxx/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;oid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1e7e2541-1297-4e1a-8b15-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;rh&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;0.AQwAoYJ9VmF_ok25VdMkTqbpdoGmBuTU86hCkLxxxxxxxxxxxx.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;sub&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1e7e2541-1297-4e1a-8b15-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;tid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;567d82a1-7f61-4da2-b955-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;uti&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;NpJBNp_iWUql96nbQP4xQA&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;ver&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;1.0&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;xms_tdbr&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;EU&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The access token above has a lifetime of 65 minutes, and the library will be in charge of refreshing it when needed.&lt;/p&gt;
&lt;h2 id=&quot;how-do-i-know-which-credential-was-used&quot;&gt;How do I know which credential was used?&lt;/h2&gt;
&lt;p&gt;You might have several credentials saved on your computer. Sometimes, this can cause a problem where the wrong one gets used, leading to access issues that can be tricky to figure out. Enabling logging is one approach to knowing which one was used.&lt;/p&gt;
&lt;p&gt;But digging through the logs is perhaps not the most optimal approach. Unfortunately, it does not provide any official other way to tell which credential was actually used. However, using some reflection magic, we can extract this information using the following hack:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetSelectedCredentials&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;credential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // This is a hack to get the selected token from DefaultAzureCredential.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // The goal is to get the value from this private field found inside DefaultAzureCredential:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //private readonly AsyncLockWithValue&amp;#x3C;TokenCredential&gt; _credentialLock;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    try&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; credType&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; credential&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (credType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _credentialLockField&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; credType.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetField&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;_credentialLock&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, System.Reflection.BindingFlags.NonPublic &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; System.Reflection.BindingFlags.Instance);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (_credentialLockField &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _credentialLock&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _credentialLockField.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cred);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _credentialLockType&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _credentialLock&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (_credentialLockType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; _valueField&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _credentialLockType.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetField&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;_value&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, System.Reflection.BindingFlags.NonPublic &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; System.Reflection.BindingFlags.Instance);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (_valueField &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; selectedCredential&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _valueField.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(_credentialLock);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                        // Use selectedCredential here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selectedCredential &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;as&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TokenCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    catch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Exception&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ex&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Failed to get selected credential&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above method can then be used like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; credentials&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DefaultAzureCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TokenCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;selectedCredential&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GetSelectedCredentials&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(cred);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;selectedCredential &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; selectedCredential.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().Name);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;what-are-the-problems-with-defaultazurecredential&quot;&gt;What are the problems with DefaultAzureCredential?&lt;/h2&gt;
&lt;p&gt;While it is highly convenient and user-friendly, it also has its drawbacks. A notable issue arises in debugging: pinpointing which one was used becomes challenging if it selects an incorrect or unexpected credential.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro tip #1&lt;/strong&gt;: I have created a handy &lt;a href=&quot;//azure/introducing-the-cloud-debugger-for-azure/&quot;&gt;open-source Cloud Debugger for Azure&lt;/a&gt; which can help with this and more. I also teach devs how to use it in my upcoming course ‘&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt;’. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro tip #2:&lt;/strong&gt; Stay Updated with the Latest &lt;a href=&quot;https://www.dotnetnews.co/&quot;&gt;.NET News &amp;#x26; Azure&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Tore Nestenius&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-are-the-alternatives-to-defaultazurecredential&quot;&gt;What are the alternatives to DefaultAzureCredential?&lt;/h2&gt;
&lt;p&gt;An alternative is the &lt;strong&gt;ChainedTokenCredential&lt;/strong&gt; type. It functions similarly to DefaultAzureCredential, with the key difference being that it starts with an empty list of TokenCredentials. With ChainedTokenCredential, you can add only the credential types relevant to your needs in the order that works best for you.&lt;/p&gt;
&lt;p&gt;This approach can be particularly advantageous for production environments, as it puts you in complete control and limits the credentials to only those you actively use.&lt;/p&gt;
&lt;p&gt;For instance, you could set it up like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; cred&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ChainedTokenCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; EnvironmentCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ManagedIdentityCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCodeCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;…&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;logging-and-diagnostics&quot;&gt;Logging and diagnostics&lt;/h2&gt;
&lt;p&gt;A simple way to get insights into what happens under the hood is to add the following code to the start of your application:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; listener&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AzureEventSourceListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(e.EventSource.Name);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (e.EventSource.Name.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StartsWith&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Azure&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Now&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;message&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    level&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: EventLevel.Verbose);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; totalTimeSw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Stopwatch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;totalTimeSw.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Start&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to capture the internal events produced by the Azure libraries, which can give you valuable insights into their inner workings.&lt;/p&gt;
&lt;h2 id=&quot;final-gotcha&quot;&gt;Final gotcha&lt;/h2&gt;
&lt;p&gt;A common issue encountered with these credential helpers revolves around the &lt;strong&gt;TenantId&lt;/strong&gt;. Often, difficulties with these types stem from a missing TenantId. To mitigate this, the TenantId can typically be specified through an optional options object or by setting environment variables. For instance, when using &lt;strong&gt;VisualStudioCredential&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCredentialOptions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    TenantId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;xxxxx&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; credentials&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; VisualStudioCredential&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(options);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Writing this blog post has been a great learning experience for me. It’s helped me better understand TokenCredential types, especially about when to use DefaultAzureCredential and ChainedTokenCredential.&lt;/p&gt;
&lt;p&gt;I gained deeper insights by downloading the Azure Identity source code from &lt;a href=&quot;https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/identity/Azure.Identity&quot;&gt;GitHub&lt;/a&gt; and turning it into my own class library. With this in place, I could add my own logging and diagnostic code. These additions really helped me get a better grasp of how everything functions.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Keys Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/&quot;&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/ai/introducing-the-coding-agent-explorer-net/&quot;&gt;Introducing the Coding Agent Explorer (.NET)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-azure-for-developers-workshop/&quot;&gt;Introduction to Azure for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/csharp-beyond-the-fundamentals/&quot;&gt;C# – Beyond the Fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed or any bugs/typos. You can find my contact details &lt;a href=&quot;//contact/&quot;&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;related-development-services&quot;&gt;Related Development Services:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/azure-cloud-development-services/&quot;&gt;Azure Cloud Development Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;Developer Coaching&lt;/a&gt; (IdentityServer, .NET, ASP.NET Core, and Azure)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/duende-identityserver-consulting-services/&quot;&gt;Duende IdentityServer Consulting Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/net-development-services/&quot;&gt;.NET Development Services&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>azure</category></item><item><title>Introducing the Data Protection API Key Ring Debugger</title><link>https://nestenius.se/net/introducing-the-data-protection-api-key-ring-debugger/</link><guid isPermaLink="true">https://nestenius.se/net/introducing-the-data-protection-api-key-ring-debugger/</guid><description>When you’re working with the Data Protection API in ASP.NET, you quickly notice how powerful and simple this service is. At the same time, you have little</description><pubDate>Tue, 12 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When you’re working with the Data Protection API in ASP.NET, you quickly notice how powerful and simple this service is. At the same time, you have little insight into how it operates. In this blog post, I will introduce a simple debugger I wrote to better demonstrate how it operates in my &lt;a href=&quot;https://tn-data.se/training/&quot;&gt;training classes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Head over to my previous blog post, &lt;a href=&quot;https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Keys Ring in Azure Key Vault&lt;/a&gt;, for details about how this API works.&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-dpapi-debugger&quot;&gt;Introducing the DPAPI Debugger&lt;/h2&gt;
&lt;p&gt;The debugger is implemented as a plain ASP.NET Core MVC Controller alongside a corresponding razor view page that will display some of the inner details of the DPAPI.&lt;/p&gt;
&lt;p&gt;On a high level, the architecture of the part of DPAPI that we are interested in, looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;the IXmlManager and IXmlEncryptor and IXmlRepository&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;209&quot; src=&quot;https://nestenius.se/_astro/xmlkeymanager-ixmlencryptor-ixmlrepository-architecture-diag.CAQWYrBI_Z27Tkay.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;encryptor&lt;/strong&gt; and &lt;strong&gt;repository&lt;/strong&gt; being used are not public information, so to extract this information, we used the new &lt;strong&gt;UnsafeAccessor&lt;/strong&gt; attribute introduced in .NET 8. You could get the same information using reflection if you don’t want to use this approach.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UnsafeAccessor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(UnsafeAccessorKind.Method, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;get_KeyEncryptor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;extern&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IXmlEncryptor&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GetKeyEncryptorField&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XmlKeyManager&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; @this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UnsafeAccessor&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(UnsafeAccessorKind.Method, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;get_KeyRepository&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;extern&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IXmlRepository&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GetKeyRepositoryField&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XmlKeyManager&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; @this&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;creating-and-revoking-aspnet-core-data-protection-keys&quot;&gt;Creating and revoking ASP.NET Core Data Protection keys&lt;/h2&gt;
&lt;p&gt;The debugger allows you to &lt;strong&gt;create&lt;/strong&gt; &lt;strong&gt;a&lt;/strong&gt; &lt;strong&gt;new&lt;/strong&gt; key and &lt;strong&gt;revoke&lt;/strong&gt; all the existing keys as well. &lt;img alt=&quot;The buttons inside the data protection debugger tool&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;412&quot; height=&quot;130&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-dpapi-debugger-key-generation-ui.BzTNZh1I_Z28dMCw.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;viewing-the-currently-used-encryptor-and-repository&quot;&gt;Viewing the currently used encryptor and repository&lt;/h2&gt;
&lt;p&gt;The debugger will show which &lt;strong&gt;encryptor&lt;/strong&gt; and &lt;strong&gt;XmlRepository&lt;/strong&gt; that is currently being used in your application.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The debugger showing the current encryptor and XmlRepository&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;615&quot; height=&quot;202&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-dpapi-summary-xmlkeymanager-azure-keyvault.CVukBmq1_25uezM.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Black and white professional headshot of a man in a dark blazer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;viewing-the-raw-persisted-keys&quot;&gt;Viewing the raw persisted keys&lt;/h2&gt;
&lt;p&gt;This view will show you what the &lt;strong&gt;key ring&lt;/strong&gt; looks like when stored in a suitable system. We can see here, for example, that a key from Azure Key Vault encrypts the keys.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of a data protection key&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;907&quot; height=&quot;638&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-dpapi-xml-key-ring-azure-key-vault-encrypted.BHY_8kP9_26jwtI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;viewing-the-unprotected-data-protection-keys&quot;&gt;Viewing the unprotected data protection keys&lt;/h2&gt;
&lt;p&gt;In the final view, you can see more details about the keys, including expiration time and if they are revoked.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Viewing the unprotected data protection keys&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1012&quot; height=&quot;698&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-dpapi-xmlkeymanager-key-details-aes256-hmacsha25.CL5FmQ_Q_Z1wGk0d.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;source-code&quot;&gt;Source code&lt;/h2&gt;
&lt;p&gt;A sample implementation and example project can be found in my GitHub repository &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/DataProtection-Debugger&quot;&gt;here&lt;/a&gt;. Feel free to submit a pull request if you have ideas for improving the code.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Writing and using this debugger has given me a better insight into how the DPAPI works and operates and hopefully it can help you too, to get a better understanding and help you to troubleshoot DAPAI problems.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/&quot;&gt;Persisting the ASP.NET Core Data Protection Keys Ring in Azure Key Vault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web security fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault</title><link>https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/</link><guid isPermaLink="true">https://nestenius.se/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/</guid><description>The ASP.NET Core Data Protection API (DPAPI) is an essential service in ASP.NET Core that is often overlooked. This post will give an overview of what it</description><pubDate>Thu, 22 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection&quot;&gt;&lt;strong&gt;The ASP.NET Core Data Protection API&lt;/strong&gt;&lt;/a&gt; (DPAPI) is an essential service in ASP.NET Core that is often overlooked. This post will give an overview of what it does and how we can persist the encryption keys in Azure Key Vault.&lt;/p&gt;
&lt;p&gt;The API’s main purpose is to encrypt and decrypt data. For example, it is used to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Protect the various &lt;strong&gt;cookies&lt;/strong&gt; issued by ASP.NET Core.&lt;br&gt;
I blogged about how the cookies are protected in the &lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt; blog post.&lt;/li&gt;
&lt;li&gt;Protecting the OpenID Connect &lt;strong&gt;state&lt;/strong&gt; and &lt;strong&gt;nonce&lt;/strong&gt; parameters.&lt;br&gt;
You can read about these two parameters in my blog post &lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The API is primarily designed to encrypt and secure short-lived data (from minutes to months), but it can also be used to encrypt data for long-term storage if needed.&lt;/p&gt;
&lt;h2 id=&quot;how-can-you-use-the-data-protection-api-to-protect-your-own-data&quot;&gt;&lt;strong&gt;How can you use the Data Protection API to protect your own data?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The API is typically used in ASP.NET Core applications, but nothing prevents you from using it in, for example, a console application. Just add the &lt;strong&gt;Microsoft.AspNetCore.DataProtection.Extensions&lt;/strong&gt; NuGet package, and then you can start using it right away.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Microsoft.AspNetCore.DataProtection.Extensions NuGet package&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;607&quot; height=&quot;81&quot; src=&quot;https://nestenius.se/_astro/microsoft-aspnetcore-dataprotection-extensions-nuget-package.CB2V6qcV_1kOOEG.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here is a sample console application using the Data Protection API to protect and unprotect a string:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Initialize the Data Protection system and store the keys c:tempkeys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; provider&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DataProtectionProvider.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; DirectoryInfo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;@&quot;c:tempkeys&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Create a data protector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; protector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; provider.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MyAppName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; encryptedData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Protect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Hello DPAPI&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decryptedData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Unprotect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(encryptedData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(encryptedData); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//CfDJ8PEJDb99-ddAhkM_GeKgmn1fcrWF9LGi10rTkU1f7IbW2y&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                                  //282ISIXZTgBBy_cL9iQ2RzxCyKbLlku7PlWVcTJw11NylWVCDq&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;                                  //kdpjmsbm9U1chfg-rYUeBNFGwB-Q5q9b1w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(decryptedData); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//Hello DPAPI&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interestingly, the API only works with strings, making it very easy to use. If you need to protect binary data, you can base64 encode it before you protect it.&lt;/p&gt;
&lt;h2 id=&quot;how-to-use-the-data-protection-api-in-aspnet-core&quot;&gt;How to use the Data Protection API in ASP.NET Core?&lt;/h2&gt;
&lt;p&gt;Using it in ASP.NET Core is also straightforward, as the Data Protection API is typically included by default in most ASP.NET Core projects. To use it in a controller, you can ask the Dependency Injection container for an instance of &lt;strong&gt;IDataProtectionProvider&lt;/strong&gt;. Like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HomeController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Controller&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; readonly&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ILogger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HomeController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;_logger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HomeController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ILogger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HomeController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                          IDataProtectionProvider&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; protectionProvider&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        _logger &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; logger;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        // Create a data protector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; protector&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protectionProvider.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MyAppName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; encryptedData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Protect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Hello DPAPI&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; decryptedData&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protector.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Unprotect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(encryptedData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(encryptedData);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        //CfDJ8Dsx1cA547pJnoAz-iXx9XYVi8GMYBHcEU9ceuB4Ep75Yr1I4pX4Rw3DWIE4H4cigg_&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        //-NvixoAoR5vl641cT-FEagETLBRoecfX011zsX7aPqf4OjxGNpCh-Bk6qfxgphw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(decryptedData); &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//Hello DPAPI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ... &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-data-protection-key-ring&quot;&gt;The Data Protection Key Ring&lt;/h2&gt;
&lt;p&gt;To protect the data, DPAPI uses an encryption key; if no key is found, it will automatically create one for us. DPAPI will introduce new keys regularly (default once every 90 days), meaning many keys will be involved over time. All these keys are stored in a key ring, and this key ring contains both the active key and the past keys.&lt;/p&gt;
&lt;p&gt;You need to keep the old keys around if you want to be able to decrypt already encrypted data.&lt;/p&gt;
&lt;h2 id=&quot;persisting-the-data-protection-key-king&quot;&gt;Persisting the Data Protection key King&lt;/h2&gt;
&lt;p&gt;The key ring should be persisted somewhere outside of your application, and there are many ready-made options to choose from, many are provided by Microsoft and the community.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Overview of the persistence options for the Data Protection API.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;588&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-data-protection-xmlkeymanager-ixmlrepository-sto.BXCYgumm_ZYqwxY.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, one missing option is Azure Key Vault. In this post, we’ll write a sample provider for Azure Key Vault to showcase how simple it is to implement.&lt;/p&gt;
&lt;h2 id=&quot;why-store-the-data-protection-key-ring-in-azure-key-vault&quot;&gt;Why store the Data Protection Key Ring in Azure Key Vault?&lt;/h2&gt;
&lt;p&gt;Most services typically require some or all of the following items to operate: HTTPS/TLS certificates, the Data Protection Key Ring (DPAPI), encryption and signing keys, as well as secrets and configuration settings:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A container requiring certificate, data protection key ring, encrytion keys, secrets and config.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;840&quot; height=&quot;479&quot; src=&quot;https://nestenius.se/_astro/web-app-container-secrets-dpapi-keyring-certificate-diagram.DeIO4UZ9_ZUsWlM.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Azure Key Vault can securely store all of these items, helping to streamline your infrastructure and simplify key management.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How Azure Key Vault can store the Key Ring, keys and secrets.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;465&quot; src=&quot;https://nestenius.se/_astro/web-app-container-azure-key-vault-secrets-diagram.KSb_KWbR_ZPtSYB.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It can even create some items for you; for example, if you use services like &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;IdentityServer&lt;/a&gt;, it can generate token signing keys. This post will focus on persisting the key ring in Azure Key Vault.&lt;/p&gt;
&lt;h2 id=&quot;encrypting-the-keys-in-the-key-ring&quot;&gt;Encrypting the keys in the key ring&lt;/h2&gt;
&lt;p&gt;Optionally, the keys inside the key ring can be encrypted at rest (when stored), and there is already support for that provided by using the &lt;a href=&quot;https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys&quot;&gt;&lt;strong&gt;Azure.Extensions.AspNetCore.DataProtection.Keys&lt;/strong&gt;&lt;/a&gt; Nuget package from Microsoft. However, storing the key ring and the encryption key in the same location might be overkill.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How to Persisting the ASP.NET Core Data Protection Key Ring in Azure Key Vault&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;920&quot; height=&quot;317&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-dpapi-azure-key-vault-key-and-secret-diagram.D2webqV6_NU7WK.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-aspnet-core-data-protection-api-and-security&quot;&gt;The ASP.NET Core Data Protection API and security&lt;/h2&gt;
&lt;p&gt;Don’t forget to always ensure that you use separate key rings for your different environments so that if one of the key rings is compromised, it can’t be used against the other environments.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Using different key rings for production, test and development.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;860&quot; height=&quot;413&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-dpapi-key-ring-per-environment-containers.BKWf5Cw2_w68MQ.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-does-a-key-in-the-data-protection-key-ring-look-like&quot;&gt;What does a key in the Data Protection key ring look like?&lt;/h2&gt;
&lt;p&gt;The key ring is by default represented as an XML document, and here’s a sample unencrypted key when it is stored in Azure Key Vault:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;root&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;key id=&quot;66533236-1e0f-40cb-b6ca-3cdb3f942331&quot; version=&quot;1&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;creationDate&gt;2024-02-12T15:47:21.2522781Z&amp;#x3C;/creationDate&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;activationDate&gt;2024-02-12T15:47:19.8787424Z&amp;#x3C;/activationDate&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;expirationDate&gt;2024-05-12T15:47:19.8787424Z&amp;#x3C;/expirationDate&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;descriptor deserializerType=&quot;Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;descriptor&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;encryption algorithm=&quot;AES_256_CBC&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;validation algorithm=&quot;HMACSHA256&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;masterKey p5:requiresEncryption=&quot;true&quot; xmlns:p5=&quot;http://schemas.asp.net/2015/03/dataProtection&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;value&gt;xtlj8GlTE2t4OeoabEBG7Ry8XVXYzaTvxsM4UPs8kQYlepNM2/6FsmxRVE+BavFKj3ULwAiuqKmyC47QmCPLYw==&amp;#x3C;/value&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/masterKey&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/descriptor&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/descriptor&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;/key&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/root&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see above that the key is not encrypted at rest.&lt;/p&gt;
&lt;p&gt;An example of a key ring where the keys are encrypted using a separate key in Azure Key Vault can look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;root&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;key id=&quot;77f04bd2-3394-4e2c-a101-d47a0cb9f04c&quot; version=&quot;1&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;creationDate&gt;2024-02-13T08:30:51.9976822Z&amp;#x3C;/creationDate&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;activationDate&gt;2024-02-13T08:30:50.8851358Z&amp;#x3C;/activationDate&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;expirationDate&gt;2024-05-13T08:30:50.8851358Z&amp;#x3C;/expirationDate&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;descriptor deserializerType=&quot;Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      .AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;descriptor&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;encryption algorithm=&quot;AES_256_CBC&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;validation algorithm=&quot;HMACSHA256&quot; /&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;encryptedSecret decryptorType=&quot;Azure.Extensions.AspNetCore.DataProtection.Keys.AzureKeyVaultXmlDecryptor, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        Azure.Extensions.AspNetCore.DataProtection.Keys, Version=1.2.2.0, Culture=neutral, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        PublicKeyToken=92742159e12e44c8&quot; xmlns=&quot;http://schemas.asp.net/2015/03/dataProtection&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;encryptedKey xmlns=&quot;&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;kid&gt;https://dpapikeyvault.vault.azure.net/keys/MyDPAPIKey/c07cda675a3446f6a752a1e50c17c0c7&amp;#x3C;/kid&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;key&gt;ZSWcsd302ouGxLFtx26MeuioxCRFhcwKxNTK7qL6TrG0d7GoiHV2B/Xtjr9CKNPgwkIUXv/mhTb/+lvwL15i/hh4kyYAPD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            /OftQjaHy3RRvHSTtkWZiCivyQr6PD8LbbZ4Wkznayzla60Ulwi+1TGhmUaGMTvpSo1mERVCK8iUEU7l8k1BCvJbEGn721nYvdB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            ZQlBraPYNLaRDxQ3JCi6Brl4UImQwetByip2YPDWcKOMVMTH+JliVGrdZclLHk97E5MCIq1G4l+C5urQ6c8C1h+vJYQJ+5581eu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            bGLXbC5pATa5bY8xCGObVj/6mZ5k9poOo+iKOU2OzL0N1h/leA==&amp;#x3C;/key&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;iv&gt;M/Jh3zr94JYs2BYikdmJiA==&amp;#x3C;/iv&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            &amp;#x3C;value&gt;3lR+foXTx4awY5KbSNzZLGXfxtu0M9vaN0gc7XBDUwwbtcDloQyuHLI9T7l/FDmtdi/KO9pas1V5VBq/yVSdufmu6JnQm&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            VIA8oHDodBD+T7+h5pM9zfP6Z6n7LcMuAxFv4T9PUEZIpThVB2dlC3pW5RNEWuFYP6oMPbk5RZw8V6dSydpBr6GWZXLUuFB3oAJB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            7+R/VCPtZypdYElZPVywdoEnLYarsoMuloqYAFz+3oP/y1XmHOZx5z1uYBw5B/3r851mYm9r7B5qU5LEV2Jq5CDMUbHgZAmca1cx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            uM5LKrBim74hYY03vtYulfxvae76aI2qNs91LmUfQTZklMskY+WA3AbEt0aUCo49n5mA+Ywffh1vpukoYRiC+7F1uC82Fea1GO0m&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;            HeGYzhHC517xbgQSON0PS3QxpOqkr8b7f8=&amp;#x3C;/value&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          &amp;#x3C;/encryptedKey&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        &amp;#x3C;/encryptedSecret&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      &amp;#x3C;/descriptor&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x3C;/descriptor&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  &amp;#x3C;/key&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&amp;#x3C;/root&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;does-azure-key-vault-have-limitations&quot;&gt;Does Azure Key Vault Have Limitations?&lt;/h2&gt;
&lt;p&gt;Yes, Azure Key Vault has some limitations around storing the key ring as a secret. The two main things to keep in mind are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We should store the key ring as one big secret in AKV, not the keys individually. This is to avoid making too many requests to AKV.&lt;/li&gt;
&lt;li&gt;There is an upper size limitation of around 25KB for a secret, meaning that there is an upper limit on the number of keys to keep in the key ring. My calculation estimates that this gives us enough space to hold about 12-13 keys, which should be sufficient for the most common use cases. This might, however, be a problem if you want to persist all the keys long-term and rotate them or revoke them regularly. Applying compression could increase this number.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;implementing-the-azure-key-vault-store&quot;&gt;Implementing the Azure Key Vault store&lt;/h2&gt;
&lt;p&gt;Implementing a key-ring store is very simple; all you need to do is implement the following interface:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// The basic interface for storing and retrieving XML elements.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IXmlRepository&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Gets all top-level XML elements in the repository.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    IReadOnlyCollection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XElement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetAllElements&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Adds a top-level XML element to the repository.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;param&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;element&quot;&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;The element to add.&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;param&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    void&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StoreElement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;XElement&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; element&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; friendlyName&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A sample implementation and example project can be found in my GitHub repository &lt;a href=&quot;https://github.com/tndataab/PublicBlogContent/tree/main/DataProtection-and-AzureKeyVault&quot;&gt;here&lt;/a&gt; . Feel free to submit a pull request if you have ideas for improving the code.&lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;Using Azure Key Vault to store the key ring is possible and implementing the persitence layer to support this is straight foward.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/introducing-the-data-protection-api-key-ring-debugger/&quot;&gt;Introducing the Data Protection API Key Ring Debugger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web security fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Improving ASP.NET Core Security By Putting Your Cookies On A Diet</title><link>https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/</link><guid isPermaLink="true">https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/</guid><description>In this blog post, we’ll explore a practical way to enhance the security of your ASP.NET Core applications by reducing the size of authentication cookies</description><pubDate>Mon, 22 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this blog post, we’ll explore a practical way to enhance the security of your ASP.NET Core applications by reducing the size of authentication cookies. Large cookies can lead to performance issues and security risks, especially when using OpenID Connect or storing sensitive information. By implementing these optimizations, you can keep your application secure, streamlined, and efficient.&lt;/p&gt;
&lt;h2 id=&quot;problem-1---large-cookies-in-aspnet-core&quot;&gt;**Problem #1 - Large cookies in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;**&lt;/p&gt;
&lt;p&gt;When you work with authentication in ASP.NET Core, you typically use the &lt;strong&gt;Cookie handler&lt;/strong&gt; to sign in the user. As a result of the sign-in, it will issue an authentication session cookie and store it in the browser.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the cookie authentication handler issues a cookie when the user is signed-in.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;255&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-cookie-handler-signin-flow-diagram.DWulesFo_18kOhg.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;However, It is crucial to manage the size of this cookie to prevent it from becoming too large. If you add OpenID Connect support, the cookie can grow even more. The code below shows a typical setup for using OpenID Connect together with the Cookie handler in ASP.NET Core:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.DefaultScheme &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.DefaultChallengeScheme &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCookie&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;https://myidentityserver.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.ClientId &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;client&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.ClientSecret &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;mysecret&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.ResponseType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;code&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.Scope.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Clear&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.Scope.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;openid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.Scope.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.Scope.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.Scope.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;offline_access&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.GetClaimsFromUserInfoEndpoint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.SaveTokens &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-does-the-openidconnect-and-cookie-handler-work-together&quot;&gt;&lt;strong&gt;How does the OpenIDConnect and Cookie handler work together?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When the user has authenticated successfully at the authorization server, the user is redirected back to the OpenIdConnect handler. As a result of this redirect, it will issue an AuthenticationTicket [&lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs%5C&quot;&gt;https://github.com/dotnet/aspnetcore/blob/main/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs\&lt;/a&gt;]. This ticket contains the following information:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The content of the the authentication ticket&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;597&quot; height=&quot;200&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-authentication-ticket-components-diagram.BFSeN5bQ_18fKt2.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;It will then ask the Cookie handler to sign in the user represented in this ticket. When it signs in the user, it encrypts this ticket and stores it in the session cookie, as shown in the picture below:&lt;/p&gt;
&lt;p&gt;Bild:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the OpenID Connect handler signs in a user.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;977&quot; height=&quot;511&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-openidconnect-cookie-handler-authentication-tick.DAKvekeq_Z2lJtMo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When we set the &lt;strong&gt;SaveTokens&lt;/strong&gt; flag to true, we ask the &lt;strong&gt;OpenIdConnect&lt;/strong&gt; handler to include the received tokens (id, access, refresh) inside the &lt;strong&gt;AuthenticationProperties&lt;/strong&gt; object. &lt;/p&gt;
&lt;p&gt;This means that the following is included in the authentication session cookie:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;What goes into the authentication cookie.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;648&quot; height=&quot;589&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-authentication-ticket-to-cookie-flow-diagram.DTj9kB1u_ITrs2.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;however-we-have-a-large-cookie-problem&quot;&gt;&lt;strong&gt;However, we have a large cookie problem!&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Storing all this data may lead to a significant increase in its size.&lt;/p&gt;
&lt;p&gt;We should remember that cookies have a maximum size limit of about 4096 bytes. To learn more about cookie limitations, visit the &lt;strong&gt;&lt;a href=&quot;http://browsercookielimits.iain.guru/&quot;&gt;Browser Cookie Limits&lt;/a&gt;&lt;/strong&gt; page and test it yourself.&lt;/p&gt;
&lt;p&gt;When the cookie grows too large, the payload is broken up into multiple cookies, as shown in the image below: &lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of how ASP.NET Core breaks up the authentication cookie into multiple cookies&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;276&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-authentication-cookie-chunks-browser-storage.C8tUlaiM_ZgHJNX.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;(Yes, the cookie example above is a bit extreme ????)&lt;/p&gt;
&lt;p&gt;Internally, the &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.cookies.chunkingcookiemanager&quot;&gt;ChunkingCookieManager&lt;/a&gt;&lt;/strong&gt; oversees this.&lt;/p&gt;
&lt;p&gt;Having too many large cookies will affect the overall web performance because all of these cookies will be included in every request to the server. With HTTP/2 and HTTP/3, large cookies are slightly less of a performance problem due to the various internal performance optimizations that they bring. &lt;/p&gt;
&lt;p&gt;We will look at how we can reduce the size of the cookies later in this blog post.&lt;/p&gt;
&lt;h2 id=&quot;problem-2---signout-in-aspnet-core&quot;&gt;**Problem #2  - Signout in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;**&lt;/p&gt;
&lt;p&gt;In a typical application, when you sign in the user, the issued cookie contains everything about the user, including the ClaimsPrincipal user object, the authentication properties, and optionally, the tokens. So far, so good!&lt;/p&gt;
&lt;p&gt;Head over to my blog post: &lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt; if you want to know more about what is stored inside the authentication cookies.&lt;/p&gt;
&lt;h2 id=&quot;what-happens-when-you-do-a-signout-in-aspnet-core&quot;&gt;&lt;strong&gt;What happens when you do a signout in ASP.NET Core?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;All that happens is that the cookie handler asks the browser to delete the cookie. That’s all. You can see for yourself in the source code &lt;strong&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/83947bf3bc084c8f6655c5cbd07d2f7bc2fbd928/src/Security/Authentication/Cookies/src/CookieAuthenticationHandler.cs#L381&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;The cookie is deleted, but what is the security issue here?&lt;/p&gt;
&lt;p&gt;The problem is that the cookie can still be used even after the user has signed out. This means that if you copy the cookie to another browser (or if a hacker gets hold of it), it can still be used to access the associated web application services until the data found inside the cookie expires.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of how a bad actor can steal your authentication cookie.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;758&quot; height=&quot;313&quot; src=&quot;https://nestenius.se/_astro/firefox-browser-session-cookie-sent-to-web-application.Bae_Yz5X_Z1XliXF.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Another complexity is that multiple items have different expiration policies inside the authentication cookie. We have the “user session” and the tokens. These have all different lifetimes and give you access to different things.&lt;/p&gt;
&lt;p&gt;So, even though you disable all the tokens or they have expired, your authentication session will still be valid. It can also be possible to still access some resources with just the session alone.&lt;/p&gt;
&lt;p&gt;How can we improve this?&lt;/p&gt;
&lt;h2 id=&quot;introducing-the-sessionstore&quot;&gt;**Introducing the SessionStore&lt;/h2&gt;
&lt;p&gt;**&lt;/p&gt;
&lt;p&gt;As mentioned earlier, the authentication ticket is typically stored inside the cookie by default. However, instead of storing it directly in the cookie, we can move this data to a separate session store on the backend, reducing the cookie size and enhancing security.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Improving security by adding a cookie session store.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;985&quot; height=&quot;418&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-oidc-cookie-handler-authentication-ticket-flow.B-0EuJGU_1Xnr85.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When implemented, the cookie handler will send the ticket to the store, and in return, it gets a session key back. This key is then stored in the cookie, and this substantially reduces the size of the cookie.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the session store issues a key that goes into the auth cookie.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;353&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-oidc-cookie-handler-session-store-flow.Ci9uRfej_Z1ip41J.webp&quot; srcset=&quot;&quot;&gt; &lt;img alt=&quot;The ASP.NET Core &amp;quot;.AspNetCore.cookie&amp;quot; authentication cookie and its value&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;752&quot; height=&quot;50&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-authentication-cookie-browser-storage-small.DUXenP7y_Z1lz0tC.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what-does-the-aspnet-core-authentication-cookie-contain&quot;&gt;&lt;strong&gt;What does the ASP.NET Core Authentication Cookie contain?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;By disabling the cookie encryption as described in my blog post &lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;, we can see that the content of the cookie is the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.....cookie..............Microsoft.AspNetCore.Authentication.Cookies-Session Id$e51c403c-ab75-4df2-9c6e-167abdb60ac8..................&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The non-printable characters have been replaced with a dot).&lt;/p&gt;
&lt;h2 id=&quot;implementing-the-iticketstore&quot;&gt;&lt;strong&gt;Implementing the ITicketStore&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To implement the session store, we need to implement the ITicketStore [&lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/ITicketStore.cs%5C&quot;&gt;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/src/ITicketStore.cs\&lt;/a&gt;] interface, which is defined as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// This provides an abstract storage mechanic to preserve the identity&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// information on the server while only sending a simple identifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// key to the client. This is most commonly used to mitigate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// issues with serializing large identities into cookies.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;/// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; interface&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ITicketStore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Store the identity ticket and return the associated key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Task&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;StoreAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ticket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Tells the store that the given identity should be updated.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RenewAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ticket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Retrieves an identity from the store for the given key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Task&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;?&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;RetrieveAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// Remove the identity associated with the given key.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    /// &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RemoveAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Implementing this interface allows you to store the data in any backend storage of your choice, as demonstrated below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The ITicketStore interface and the default storage implementation.s&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;467&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-iticketstore-cookie-handler-storage-backends.4j98mIxL_AO7d1.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here’s an example of a simple in-memory store implementation in ASP.NET Core:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;internal&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MySessionStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ITicketStore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ConcurrentDictionary&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;mytickets&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MySessionStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RemoveAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Debug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MySessionStore.RemoveAsync Key=&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (mytickets.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContainsKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            mytickets.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TryRemove&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; _);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RenewAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ticket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Debug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MySessionStore.RenewAsync Key=&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;,&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		          ticket &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot; + ticket.AuthenticationScheme)&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        mytickets[key] &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ticket;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        return Task.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    public Task&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; RetrieveAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Debug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MySessionStore.RetrieveAsync Key=&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (mytickets.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ContainsKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            var ticket &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mytickets[key];&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            return Task.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ticket);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            return Task.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AuthenticationTicket&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    public Task&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&gt;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StoreAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(AuthenticationTicket ticket)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        var key &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        var result &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; mytickets.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;TryAdd&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key, ticket);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (result)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; username &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ticket&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Principal&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Unknown&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Log.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Debug&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MySessionStore.StoreAsync ticket=&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; username &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;, key=&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            return Task.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;FromResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(key);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            throw&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Exception&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Failed to add entry to the MySessionStore.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it!&lt;/p&gt;
&lt;p&gt;One implementation using the .NET MemoryCache can be found &lt;strong&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Cookies/samples/CookieSessionSample/MemoryCacheTicketStore.cs&quot;&gt;here&lt;/a&gt;&lt;/strong&gt;. More implementations can be found on Github and on NuGet.&lt;/p&gt;
&lt;h2 id=&quot;using-the-session-store&quot;&gt;&lt;strong&gt;Using the Session Store&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To use it, we need to create an instance and pass it to the &lt;code&gt;SessionStore&lt;/code&gt; property of the cookie authentication handler, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCookie&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.SessionStore &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MySessionStore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;why-using-a-session-store-improves-security&quot;&gt;&lt;strong&gt;Why using a session store improves security&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;As discussed, when you sign out, the cookie is removed from the browser by default. However, the cookie is still valid and can be reused until its content expires.&lt;/p&gt;
&lt;p&gt;By introducing the session store, the entry stored in it is automatically removed at sign-out. This means that even if the cookie is stolen, it can’t be reused! The cookie handler does this for us automatically.&lt;/p&gt;
&lt;h2 id=&quot;additional-possibilities-with-a-session-store&quot;&gt;**Additional Possibilities with a Session Store&lt;/h2&gt;
&lt;p&gt;**&lt;/p&gt;
&lt;p&gt;What other possibilities does the introduction of the session store enable? &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can create a “Where you’re signed in” page, like this page on Google:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Example of a Where you’re signed in page.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;707&quot; height=&quot;766&quot; src=&quot;https://nestenius.se/_astro/google-account-signed-in-devices-windows-iphone-sessions.KhS24yPG_Zg623Q.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metrics and audit logs&lt;/strong&gt; We could add logging or metrics of all the method calls to the session store to get insights into how the users use our system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sign out users remotely&lt;/strong&gt; For example, if an employee leaves your company, you can then delete all the entries in the database for this employee.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Emergency signout button.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;512&quot; height=&quot;512&quot; src=&quot;https://nestenius.se/_astro/emergency-stop-button-sign-out.YIUyVRaY_Z1AzpXj.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aspnet/Security/blob/master/samples/CookieSessionSample/MemoryCacheTicketStore.cs&quot;&gt;Sample SessionStore using the MemoryCache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ml-software.ch/posts/implementing-a-custom-iticketstore-for-asp-net-core-identity&quot;&gt;Implementing a custom ITicketStore for ASP.NET Core Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.nuget.org/packages?q=ITicketStore&quot;&gt;TicketStores on NuGet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/&quot;&gt;Storing ASP.NET core identity authorization tickets in Redis.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/&quot;&gt;Demystifying OpenID Connect’s State and Nonce Parameters in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/&quot;&gt;Exploring what is inside the ASP.NET Core cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web security fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed, or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Demystifying OpenID Connect&apos;s State and Nonce Parameters in ASP.NET Core</title><link>https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/</link><guid isPermaLink="true">https://nestenius.se/net/demystifying-openid-connects-state-and-nonce-parameters-in-asp-net-core/</guid><description>In the world of web application security, OpenID Connect plays a key role in streamlining authentication processes. But what makes it really tick? In this</description><pubDate>Wed, 13 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the world of web application security, &lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;&lt;strong&gt;OpenID Connect&lt;/strong&gt;&lt;/a&gt; plays a key role in streamlining authentication processes. But what makes it really tick? In this blog post, we dive deep into two critical security features of OpenID Connect – the &lt;strong&gt;state&lt;/strong&gt; and &lt;strong&gt;nonce&lt;/strong&gt; parameters – and how they are used in ASP.NET Core.&lt;/p&gt;
&lt;p&gt;This simplified diagram tries to show how the state and nonce are used when a user authenticates using OpenID Connect:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Overview of how the state and nonce parameter is used durign the authentication flow.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;609&quot; src=&quot;https://nestenius.se/_astro/openid-connect-state-nonce-flow-client-sts-id-token.D_Oo9Ijx_Z2cVRH3.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the image above, the client should verify that the returned state value matches the expected value and that the nonce inside the ID-token also matches the expected value.&lt;/p&gt;
&lt;h2 id=&quot;challenging-the-user-in-aspnet-core&quot;&gt;Challenging the user in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;In ASP.NET Core, when a user attempts to access a secured part of your application, the OpenID Connect handler initiates a &lt;strong&gt;challenge&lt;/strong&gt; request. This can happen automatically via the authorization middleware or manually through the &lt;strong&gt;ChallengeAsync&lt;/strong&gt; method:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HttpGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Login&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (User.Identity.IsAuthenticated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HttpContext.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ChallengeAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This challenge triggers a redirect to the authorization server, such as &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt;, with a URL crafted with various parameters, including the client_id, redirect_uri, response_type, and, notably, the state and nonce parameters.&lt;/p&gt;
&lt;p&gt;The redirect URL can look something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;https://identityservice.secure.nu/connect/authorize?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=localhost-addoidc-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;redirect_uri=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;https%3A%2F%2Flocalhost%3A5001%2Fsignin-oidc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_type=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;prompt=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;consent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;scope=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;openid%20profile%20email%20employee_info%20offline_access%20api&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;3LEckeTVCxl4TD12nxptlbiIgTzEEMfUqBa-sZo8foY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;code_challenge_method=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;response_mode=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;form_post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638357382195073041.NzViMTM2MzktNWFhMi00MDhjLWIzODYtMDBkYzAxMDE2MmR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kYjY3NzdhYTEtZGQ0Mi00MTM4LWIwOTgtZjMyNmFlOTFjYzZk&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;state=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;AQAAAAQAAAAJLnJlZGlyZWN0DC9Vc2VyL0xvZ2luPw1jb2RlX3ZlcmlmaWVyK3pPNzREaFJI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;NndPbTdOSWZHZDZ3bkc0MDB0MEdNR3dMRGtmblF6NnVOMzgFLnhzcmYra2Q3OXk2N2E3Nk5DZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;3ZuY1RfNGdMQVgyeHVhMW12YjFrTkRwdnJBUXVpbx5PcGVuSWRDb25uZWN0LkNvZGUuUmVkaX&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;JlY3RVcmkiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMS9zaWduaW4tb2lkYw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-SKU=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;ID_NET8_0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;&amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;x-client-ver=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;7.0.0.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact content depends on your OpenID-Connect middleware configuration.&lt;/p&gt;
&lt;h2 id=&quot;the-state-parameter-in-openid-connect&quot;&gt;The state parameter in OpenID Connect&lt;/h2&gt;
&lt;p&gt;The state parameter is crucial to an OpenID Connect authorization request. But what is its main purpose?&lt;/p&gt;
&lt;p&gt;In OpenID Connect, the &lt;strong&gt;state&lt;/strong&gt; parameter serves as an opaque value created by the client. Its primary function is maintaining the state between the initial request and the callback, acting as a shield against cross-site request forgery attacks during the authentication process.&lt;/p&gt;
&lt;p&gt;While some implementations keep the &lt;strong&gt;state&lt;/strong&gt; as a random value, that is verified during the callback. Other implementations injecting “data” into the state parameter. By doing this, the client doesn’t have to remember this information elsewhere, making the client stateless.&lt;/p&gt;
&lt;h2 id=&quot;looking-inside-the-state-parameter&quot;&gt;Looking inside the state parameter &lt;/h2&gt;
&lt;p&gt;The OIDC handler in ASP.NET Core stores data in the state parameter, which is encrypted and secured using the &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction&quot;&gt;Data Protection API&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;However, we can “bypass” this encryption and issue an unencrypted state parameter. To do this, we can add our own transparent data protector and implement it like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IDataProtector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IDataProtector&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; purpose&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Protect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;plaintext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; plaintext;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Unprotect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;protectedData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protectedData;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we can tell the OpenIDConnect handler to replace the default encryption protector with this one instead:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.StateDataFormat &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PropertiesDataFormat&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding this allows us to peek inside the state parameter:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET https://identityservice.secure.nu/connect/authorize?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    client_id=localhost-addoidc-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &amp;#x26;state=AQAAAAQAAAAJLnJlZGlyZWN0DC9Vc2VyL0xvZ2luPw1jb2RlX3ZlcmlmaWVyK0dISVBiMmJG&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           SHNfWDdGRjRmSm1aZDBKR1BXbm5BeXllVWlsUXJnMjE2NE0FLnhzcmYrLTMxWktMb0N3TlVUcnd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           EQTVfSHRkZExPbUtVbTVkM0piMC14X3ZmSm1RWR5PcGVuSWRDb25uZWN0LkNvZGUuUmVkaXJl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;           Y3RVcmkiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMS9zaWduaW4tb2lkYw&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we decode the state parameter using a tool like &lt;strong&gt;base64 decode&lt;/strong&gt; &lt;a href=&quot;https://www.base64decode.org&quot;&gt;https://www.base64decode.org&lt;/a&gt; , then we get a state that looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.................redirect./User/Login? &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;code_verifier+GHIPb2bFHs_X7FF4fJmZd0JGPWnnAyyeUilQrg2164M..xsrf+- &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;31ZKLoCwNUTrwDA5_HtddLOmKUm5d3Jb0- &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;x_vfJmQY.OpenIdConnect.Code.RedirectUri&quot;https://localhost:5001/signin-oidc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The non-printable characters have been replaced with a dot).The above can be a bit hard to read, a cleaned-up version looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[0]: {[.redirect, /User/Login?]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[1]: {[code_verifier, GHIPb2bFHs_X7FF4fJmZd0JGPWnnAyyeUilQrg2164M]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2]: {[.xsrf, -31ZKLoCwNUTrwDA5_HtddLOmKUm5d3Jb0-x_vfJmQY]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[3]: {[OpenIdConnect.Code.RedirectUri, https://localhost:5001/signin-oidc]}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;can-i-add-custom-properties-here&quot;&gt;Can I add custom properties here?&lt;/h2&gt;
&lt;p&gt;Yes, if you provide an instance of the &lt;strong&gt;AuthenticationProperties&lt;/strong&gt; with the challenge, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;HttpGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Login&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (User.Identity.IsAuthenticated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;==&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; properties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AuthenticationProperties&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            RedirectUri &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Items &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;IpAddress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;192.168.0.3&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ComputerName&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;MyComputer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ApiKey&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Summer2023&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Language&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;English&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; HttpContext.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ChallengeAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(properties);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you might end up with a state parameter that looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;… &amp;#x26;state=AQAAAAgAAAAJLnJlZGlyZWN0AS8JSXBBZGRyZXNzCzE5Mi4xNjguMC4zDENvbXB1dGVyTm &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;FtZQpNeUNvbXB1dGVyBkFwaUtleQpTdW1tZXIyMDIzCExhbmd1YWdlB0VuZ2xpc2gNY29kZV92ZXJ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;pZmllcitpVGw1UnczYno1SnYxNm90ektIbHpHT0RGSjczblJ6dzRSbDQ4Zkh4RGFFBS54c3JmK1hOd3R &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;fd3otMjlpVGtGYklXN2ZsWGNwdmplenF1bElWYkt5alQzWWIzZ00eT3BlbklkQ29ubmVjdC5Db2RlLlJl &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ZGlyZWN0VXJpImh0dHBzOi8vbG9jYWxob3N0OjUwMDEvc2lnbmluLW9pZGM …&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you base64 decode it and then clean it up, you will get the following information:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[0]: {[.redirect, /]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[1]: {[IpAddress, 192.168.0.3]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[2]: {[ComputerName, MyComputer]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[3]: {[ApiKey, Summer2023]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[4]: {[Language, English]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[5]: {[code_verifier, iTl5Rw3bz5Jv16otzKHlzGODFJ73nRzw4Rl48fHxDaE]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[6]: {[.xsrf, XNwt_wz-29iTkFbIW7flXcpvjezqulIVbKyjT3Yb3gM]}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[7]: {[OpenIdConnect.Code.RedirectUri, https://localhost:5001/signin-oidc]}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;AuthenticationProperties&lt;/strong&gt; that you passed to the initial challenge, will later be available in the &lt;strong&gt;ClaimsPrincipal&lt;/strong&gt; user object after the user is signed in.&lt;/p&gt;
&lt;h2 id=&quot;the-nonce-parameter-in-openid-connect&quot;&gt;The nonce parameter in OpenID Connect&lt;/h2&gt;
&lt;p&gt;The nonce parameter in OpenID Connect is crucial for associating a client session with the ID token and is used to mitigate replay attacks.&lt;/p&gt;
&lt;p&gt;In ASP.NET Core, it’s generated by the &lt;strong&gt;GenerateNonce&lt;/strong&gt; method, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; virtual&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; GenerateNonce&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    LogHelper.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;LogVerbose&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(LogMessages.IDX21328);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; nonce&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Convert.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToBase64String&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Encoding.UTF8.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;GetBytes&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;() &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                                                 Guid.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (RequireTimeStampInNonce)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; DateTime.UtcNow.Ticks.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(CultureInfo.InvariantCulture) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;.&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nonce;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; nonce;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The method is found in the &lt;a href=&quot;https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/OpenIdConnectProtocolValidator.cs#L85&quot;&gt;&lt;strong&gt;OpenIdConnectProtocolValidator&lt;/strong&gt;&lt;/a&gt; class.&lt;/p&gt;
&lt;p&gt;The nonce generated by this method is a string that consists of a timestamp and a random value; it can look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;638357413236076453.NjY2MmZlZTctOTU2OC00ZmNlLTk5NWUtMWRmODMxZDY4NTFhYmRmYjI3ODgtN TU3ZS00NjZiLWJjMGMtNWFlOWNkOGQ3M2Vi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-is-the-nonce-parameter-handled-in-aspnet-core&quot;&gt;&lt;strong&gt;How is the nonce parameter handled in ASP.NET Core?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Upon &lt;strong&gt;challenging&lt;/strong&gt; the user, a &lt;strong&gt;nonce&lt;/strong&gt; is generated and stored as a cookie, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.AspNetCore.OpenIdConnect.Nonce &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.NjM4MzU3NDE2MTcwODk2NjQ2LllUWmtOak5qWm1RdE5UTmpaUzAwTkRNeExUZzBNRFV0Wm1aalpHVmxOekk &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;1WlRsak5XWmhaR000TmpndFpEY3pPQzAwTXpRd0xXRTFNR010TTJRd01qQm1aV0kzT1ROag=N;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;expires=Thu, 16 Nov 2023 14:42:08 GMT; path=/signin-oidc; secure; samesite=none; httponly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cookie name is “&lt;strong&gt;.AspNetCore.OpenIdConnect.[Nonce]&lt;/strong&gt;” and the interesting thing here is that the cookie name contains the nonce value. The nonce here is also protected using the Data Protection API.&lt;/p&gt;
&lt;p&gt;Similar to what we did before, we can introduce the transparent protector by setting the &lt;strong&gt;StringDataFormat&lt;/strong&gt; property.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}).&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;	//...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.StateDataFormat &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; PropertiesDataFormat&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.StringDataFormat &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; SecureDataFormat&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; StringSerializer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding this will make the handler store the nonce in the cookie in unprotected form. However, this is not useful, as there’s little interesting information to see in the nonce cookie.&lt;/p&gt;
&lt;p&gt;Besides storing the nonce in the cookie, it is also included in its raw form in the authentication request to the authorization server:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://identityservice.secure.nu/connect/authorize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;client_id=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;localhost-addoidc-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    &amp;#x26;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;nonce=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;638357416170896646.YTZkNjNjZmQtNTNjZS00NDMxLTg0MDUtZmZjZGVlNzI5ZTljNWZhZGM4NjgtZD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;          czOC00MzQwLWE1MGMtM2QwMjBmZWI3OTNj&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;In this blog post, we have explored what is inside the encrypted &lt;strong&gt;state&lt;/strong&gt; parameter that is passed to the authorization server when the user is challenged. We also saw that we can pass custom AuthenticationProperties to the challenge operation, and these properties will later end up in the ClaimsPrincipal user object.&lt;/p&gt;
&lt;p&gt;We also explored the &lt;strong&gt;nonce&lt;/strong&gt;, a random string that we store securely in a cookie and pass in its raw form to the authorization server. The nonce ties the ID-token to the initial request made to the authorization server. With the nonce, the client knows the token is generated for itself, and it won’t consume a token injected by a malicious party.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection&quot;&gt;How to: Use Data Protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview&quot;&gt;Configure ASP.NET Core Data Protection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://auth0.com/docs/secure/attack-protection/state-parameters&quot;&gt;Prevent Attacks and Redirect Users with OAuth 2.0 State Parameters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/keycloak/the-importance-of-the-state-parameter-in-oauth-5419c94bef4c&quot;&gt;The importance of the “state” parameter in OAuth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/bearertoken-the-new-authentication-handler-in-net-8/&quot;&gt;BearerToken: The new Authentication handler in .NET 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/debugging-jwtbearer-claim-problems-in-asp-net-core/&quot;&gt;Debugging JwtBearer Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;OpenID Connect for Developers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-openid-connect-and-oauth/&quot;&gt;Introduction to OpenID Connect and OAuth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-identityserver-and-openid-connect/&quot;&gt;Introduction to IdentityServer and OpenID-Connect&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed, or any bugs/typos. You can find my contact details &lt;a href=&quot;//contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>openid-connect</category></item><item><title>Exploring what is inside the ASP.NET Core cookies</title><link>https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/</link><guid isPermaLink="true">https://nestenius.se/net/exploring-what-is-inside-the-asp-net-core-cookies/</guid><description>ASP.NET Core generates various types of cookies, such as authentication, antiforgery, and session cookies. In this blog post, we’ll take a closer look at</description><pubDate>Wed, 22 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ASP.NET Core generates various types of &lt;strong&gt;cookies&lt;/strong&gt;, such as &lt;strong&gt;authentication&lt;/strong&gt;, &lt;strong&gt;antiforgery&lt;/strong&gt;, and &lt;strong&gt;session cookies&lt;/strong&gt;. In this blog post, we’ll take a closer look at what information these cookies store, how they function, and the security measures used to protect them, including encryption and the &lt;strong&gt;Data Protection API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Overview of the core cookies issued by ASP.NET Core.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;358&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-cookie-types-browser-diagram.Bj1mAOLO_ZH04qw.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;protecting-the-aspnet-core-cookies&quot;&gt;Protecting The ASP.NET Core Cookies&lt;/h2&gt;
&lt;p&gt;The content of these cookies is protected through a combination of encryption and signing mechanisms. These protective measures ensure the confidentiality and integrity of the information stored within the cookies.&lt;/p&gt;
&lt;p&gt;The protection is managed by the &lt;strong&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction&quot;&gt;Data Protection API&lt;/a&gt;&lt;/strong&gt; (DPAPI) as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the cookies are encypted an secured using the Data Protection API.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;254&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-data-protection-api-cookie-encrypt-decrypt-flow.CVtqlYNP_Z8hXb.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The Data Protection API in ASP.NET Core is a framework for securing sensitive data, managing encryption, and facilitating key rotation. It offers a unified interface for encryption, decryption, and signing that enhances application data security. How the Data Protection API works is beyond the scope of this blog post.&lt;/p&gt;
&lt;h2 id=&quot;the-aspnet-core-authentication-cookie&quot;&gt;The ASP.NET Core Authentication Cookie&lt;/h2&gt;
&lt;p&gt;The Authentication Cookie in ASP.NET Core maintains user authentication across HTTP requests. When a user logs in, the authentication system typically issues a cookie to the client, appearing as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.cookie=CfDJ8MSO_2XEvwalH...;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;expires=Thu, 30 Nov 2023 09:38:16 GMT; path=/; secure; samesite=lax; httponly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Data Protection API encrypts this cookie, making its contents inaccessible and tamperproof.&lt;/p&gt;
&lt;p&gt;So, how can we peek inside this cookie?&lt;/p&gt;
&lt;p&gt;Luckily for us, we can turn off this protection by providing a custom transparent data protector, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IDataProtector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IDataProtector&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; purpose&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Protect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;plaintext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; plaintext;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Unprotect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;protectedData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protectedData;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then add this protector to the authentication cookie handler:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCookie&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.DataProtectionProvider &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means that the issued cookies will not be protected at all. To test this and issue a new unsecured cookie, we first need to sign in a user. Wecan do this by using this simple test code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; claims&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        //Standard claims&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ClaimTypes.Name, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Tore Nestenius&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ClaimTypes.Country, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Sweden&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(ClaimTypes.Email, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;tore@tn-data.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        //Custom claims&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;JobTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Consultant and trainer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;JobLevel&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Senior&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;webpage&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://www.tn-data.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ClaimsIdentity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;claims&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: claims,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;                                      authenticationType&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;									  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ClaimsPrincipal&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;identity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: identity);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; authProperties&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AuthenticationProperties&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //Sign-in the user&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;SignInAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, user, authProperties);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&amp;#x3C;!DOCTYPE html&gt;&amp;#x3C;body&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; context.Response.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteAsync&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Logged in!&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we create a ClaimsPrincipal user object and ask the cookie handler to sign this user. By doing so, the handler will issue a new unprotected authentication cookie.&lt;/p&gt;
&lt;p&gt;Running this code will issue a new cookie that can look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  .AspNetCore.cookie=BQAAAAZjb29raWUBAAAABmNvb2tpZQEAAQAGAAAAAQAOVG9yZSBOZXN0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ZW5pdXMBAAEA AQAAAAAAPWh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2x&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;haW1zL2NvdW50 cnkGU3dlZGVuAQABAAEAAAAAAEJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1Lz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;A1L2lkZW50aXR5L2 NsYWltcy9lbWFpbGFkZHJlc3MPdG9yZUB0bi1kYXRhLnNlAQABAAEAAAAAAAhKb2JUaXRsZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;RZDb25zdWx0YW50IGFuZ CB0cmFpbmVyAQABAAEAAAAAAAhKb2JMZXZlbAZTZW5pb3IBAAEAAQAAAAAAB3dlYnBh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Z2UWaHR0cHM6Ly93d3c udG4tZGF0YS5zZQEAAQABAAAAAAAAAAEAAAADAAAACy5wZXJzaXN0ZW50AAcuaXNzdWV&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;kHVRodSwgMTYgTm92 IDIwMjMgMDk6NTc6MDQgR01UCC5leHBpcmVzHVRodSwgMzAgTm92IDIwMjMgMDk6NTc6MD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;QgR01U; expires=Thu, 30 Nov 2023 09:57:04 GMT; path=/; secure; samesite=lax; httponly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To peek inside the cookie, we need to first base64 decode it. We can do that using a tool like &lt;a href=&quot;https://www.base64decode.org&quot;&gt;&lt;strong&gt;Base64Decode&lt;/strong&gt;&lt;/a&gt;, and if we do that, we will see the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;......cookie.....cookie...........Tore Nestenius..........=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country.Sweden..........B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.tore@tn-data.se&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...........JobTitle.Consultant and trainer...........JobLevel.Senior...........&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;webpage.https://www.tn-data.se......................persistent...issued.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Thu, 16 Nov 2023 09:57:04 GMT..expires.Thu, 30 Nov 2023 09:57:04 GMT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The non-printable characters have been replaced with a dot).&lt;/p&gt;
&lt;p&gt;This shows that inside the cookie we find the entire authentication ticket (consisting of the ClaimsPrincipal and the AuthenticationProperties).&lt;/p&gt;
&lt;h2 id=&quot;the-aspnet-core-session-cookie&quot;&gt;The ASP.Net Core Session Cookie&lt;/h2&gt;
&lt;p&gt;The session service in ASP.NET Core is a mechanism for managing user-specific data across  requests, often being used for scenarios like maintaining a shopping cart. This service issues a protected cookie to the browser to keep track of the data.  It is important to know that this cookie and its purpose are unrelated to the earlier authentication cookie.&lt;/p&gt;
&lt;p&gt;Your application can ask the session service to remember temporary data for the current user, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the use of a session store can improve security.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;441&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-session-cookie-flow-diagram.M0ac-9ae_Zw00CD.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;To implement the session service, register the service and add it to the request pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Register the service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddSession&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...Options…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//..&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseSession&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A sample session cookie could look like:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.AspNetCore.Session=CfDJ8MSO%2F2XEvalHlF%2Fgv69RLqD6mFWeTVC1MG3dYxNWftq625VyGyqF%2BeIq2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;xaqgm1cd4McTp0ydSLcRYraIA4%2F%2Bn89FKFhz567AcC%2FeSnwabg4eRrlFAeWLFBq0K8zl2ISdMPcY0pj%&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;2BtAJQgC5NIte76QR4TlheM1ZhsD98WAdAvKM; path=/; samesite=lax; httponly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This cookie is also protected using the &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction&quot;&gt;&lt;strong&gt;Data Protection API&lt;/strong&gt;&lt;/a&gt;; however, there is no way to provide a custom transparent data protector, as we did in the previous example.&lt;/p&gt;
&lt;h2 id=&quot;peeking-inside-the-session-cookie&quot;&gt;Peeking inside the Session Cookie?&lt;/h2&gt;
&lt;p&gt;One option is to download and modify the code for the session middleware. The source code is found on &lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Middleware/Session&quot;&gt;GitHub&lt;/a&gt;, and it is straightforward to modify. When I introduce the transparent protector, the unencrypted cookie will look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.Session=Y2M1NGVjZGItZDhjNi0yYTI1LTM2N2UtMjhhNmM4ZWRhMzMw;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;path=/; samesite=lax; httponly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This cookie is also base64 encoded, so decoding the cookie will result in the following data:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cc54ecdb-d8c6-2a25-367e-28a6c8eda330&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This GUID acts as a session key used to look up the &lt;strong&gt;session object&lt;/strong&gt; for the current request. Where the data is stored depends on how you have configured the session system.&lt;/p&gt;
&lt;p&gt;The session system in ASP.NET Core provides various storage options, including in-memory storage, distributed cache, and external storage providers, which allowsdevelopers to choose the most suitable method for their application’s scalability and performance needs.&lt;/p&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Black and white professional headshot of a man in a suit jacket&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-aspnet-core-antiforgery-cookie&quot;&gt;The ASP.NET Core Antiforgery Cookie&lt;/h2&gt;
&lt;p&gt;This cookie is crucial forsafeguarding against Cross-site Request Forgery (CSRF) attacks. This cookie operates on a token-based system, where the token is stored within the cookie and also embedded in HTML forms. When a form is submitted, the token in the form data must correspond with the token in the cookie to ensure security.&lt;/p&gt;
&lt;p&gt;Additionally, the Data Protection API secures this cookie. The implementation of this protection is handled by the DefaultAntiforgeryTokenSerializer class, which you can explore in detail &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenSerializer.cs&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt; .&lt;/p&gt;
&lt;p&gt;Here’s an example of how this cookie can beset in ASP.NET Core:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.Antiforgery.YCD23e8AirM=CfDJ8MSO_2XEvalHlF_gv69RLqDarftldkzWvbxvac&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;LnZ4WtaTDcgSCPmxQdmK8OHbGfMIUvV0VhcFkx4Ys8jPppklGBUGWRTsXcwxhMBl7nqZA0s_xYN5jJq3J7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;LrH8EikY82bOYmSoA_wEYCxkcrBEEAs; path=/; samesite=strict; httponly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;				&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;			&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;While this blog post does not go into the specifics of the token&apos;s internal structure, those interested in this &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;can refer to the Default [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Antiforgery&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Token&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Serializer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//github.com/dotnet/aspnetcore/blob/main/src/Antiforgery/src/Internal/DefaultAntiforgeryTokenSerializer.cs) &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;class for an in-depth understanding of how the token is constructed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;## Conclusions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;This post covers ASP.NET Core cookies, specifically the authentication, session, and antiforgery cookies, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;shedding light on their contents. It emphasizes the security aspects involving encryption and signing through the Data Protection API.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- **The authentication cookie**  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    This cookie contains the entire ClaimsPrincipal user object and the associated authentication properties.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- **The antiforgery cookie**  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    The token of this cookie is used to protect HTML forms.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- **The session cookie**  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    This cookie contains the session key that the session service can use to look up the stored data for a given user.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Remember that you should never disable data protection in real production applications!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;## Resources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;How&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; to:&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Use&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Data&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Protection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Configure&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Data&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Protection&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Storing&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Data&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Protection&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Key&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Ring&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Azure&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Key&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Vault&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//www.edument.se/post/storing-the-asp-net-core-data-protection-key-ring-in-azure-key-vault?lang=en)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; cookie&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; authentication&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; without&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Identity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;## More posts by the author&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Persisting&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Data&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Protection&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Key&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Ring&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Azure&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Key&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Vault&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](/net/persisting-the-asp-net-core-data-protection-key-ring-in-azure-key-vault/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;BearerToken:&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; The&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Authentication&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; handler&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; .NET&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 8&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](/net/bearertoken-the-new-authentication-handler-in-net&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;-8&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Debugging&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; JwtBearer&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Problems&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](/net/debugging-jwtbearer-claim-problems-in-asp-net-core/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Debugging&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; OpenID&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Connect&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Claim&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Problems&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](/net/missing-openid-connect-claims-in-asp-net-core/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Introducing&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; the&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Cloud&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Debugger&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; for&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Azure&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](/azure/introducing-the-cloud-debugger-for-azure/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;## Related Training Workshops&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Building&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; APIs&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//tn-data.se/courses/building-asp-net-core-apis/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;ASP.NET&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Core&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; fundamentals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//tn-data.se/courses/asp-net-core-fundamentals/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;- [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Web&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; security&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; fundamentals&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//tn-data.se/courses/web-security-fundamentals/)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;## Feedback, comments, found any bugs?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Let me know if you have any feedback, anything I missed, or any bugs/typos. You can find my contact details [&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;here&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;](/contact/).&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Debugging cookie problems in ASP.NET Core</title><link>https://nestenius.se/net/debugging-cookie-problems/</link><guid isPermaLink="true">https://nestenius.se/net/debugging-cookie-problems/</guid><description>Having answered over 1000 questions on Stack Overflow, I’ve found that cookie-related issues are a frequent challenge for developers using ASP.NET Core</description><pubDate>Mon, 09 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;troubleshooting-cookie-problems-in-aspnet-core&quot;&gt;Troubleshooting cookie problems in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;Having answered over 1000 questions on &lt;a href=&quot;https://stackoverflow.com/users/68490/tore-nestenius&quot;&gt;Stack Overflow&lt;/a&gt;, I’ve found that cookie-related issues are a frequent challenge for developers using ASP.NET Core, especially when implementing authentication and OpenID Connect.&lt;/p&gt;
&lt;p&gt;Cookie problems can, in my experience, be categorized into the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Browser Rejection&lt;/strong&gt;&lt;br&gt;
Cookies provided by the server that aren’t accepted by the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Browser Omission&lt;/strong&gt;&lt;br&gt;
Cookies in the browser are not included in requests to the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lost cookies&lt;/strong&gt;&lt;br&gt;
Cookies that are lost on the way to the server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this post, we’ll explore common ASP.NET Core cookie problems, such as browser rejection, missing cookies in requests, and lost cookies. You’ll learn practical tips to identify, diagnose, and resolve these issues, ensuring smoother authentication flows and a more reliable application experience.&lt;/p&gt;
&lt;h2 id=&quot;cookies-rejected-by-the-browser&quot;&gt;Cookies rejected by the browser&lt;/h2&gt;
&lt;p&gt;When setting a cookie, we use the &lt;strong&gt;set-cookie&lt;/strong&gt; response header, often accompanied by the &lt;strong&gt;secure&lt;/strong&gt; and &lt;strong&gt;samesite&lt;/strong&gt; attributes. Below is an example illustrating the setting of five common cookies:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 200&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; OK Set-Cookie: NormalCookie=NormalValue;Path=/ &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NormalCookieWitSecure=NormalValueSecure;Path=/;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;secure Set-Cookie: StrictCookie=StrictValue;Path=/;SameSite=Strict &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; LaxCookie=LaxValue;Path=/;SameSite=Lax&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; NoneCookie=NoneValue;Path=/;SameSite=None;Secure …&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Note: This article will not discuss the details of the &lt;strong&gt;path&lt;/strong&gt;, &lt;strong&gt;domain&lt;/strong&gt;, and the &lt;strong&gt;HttpOnly&lt;/strong&gt; attribute. The &lt;strong&gt;HttpOnly&lt;/strong&gt; attribute is all about restricting JavaScript from accessing cookies.). I recommend setting the &lt;strong&gt;HttpOnly&lt;/strong&gt; attribute on every cookie that never needs to be accessed by JavaScript.&lt;/p&gt;
&lt;h2 id=&quot;how-can-i-tell-if-the-browser-accepts-a-cookie&quot;&gt;How can I tell if the browser accepts a cookie?&lt;/h2&gt;
&lt;p&gt;The first step in troubleshooting cookie problems is to verify that the browser has accepted the cookie.&lt;/p&gt;
&lt;p&gt;To see which cookies it has received and accepted, open the browser developer tools (&lt;strong&gt;F12&lt;/strong&gt;) in &lt;strong&gt;Chrome&lt;/strong&gt; and look under &lt;strong&gt;Application -&gt; Storage -&gt; Cookies&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How to view the cookies for the current page.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;643&quot; height=&quot;301&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-application-cookies-panel-samesite.DnXz5jiQ_gKPKq.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;If the expected cookies are not found here, then head over to the &lt;strong&gt;Network&lt;/strong&gt; tab, locate the request that sets the cookie, and then click on the &lt;strong&gt;Cookies&lt;/strong&gt; sub-tab:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How to view the cookies for a given request in the network tab.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;364&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-network-cookies-tab-response-set-cookie.DB87n3VC_ZcrrtB.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Rejected cookies are highlighted in yellow. Hovering over the information (i) icon displays reasons for the blockage. As shown in the example below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of rejected cookies in the browser&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;838&quot; height=&quot;144&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-set-cookie-blocked-secure-attribute-http.NIt1W2lE_1Mj3cA.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can also activate the “&lt;strong&gt;Has blocked cookies&lt;/strong&gt;” checkbox to only show requests with blocked response cookies.&lt;/p&gt;
&lt;p&gt;Some of the most common reasons for blocking are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using &lt;strong&gt;samesite=none&lt;/strong&gt; without the &lt;strong&gt;secure&lt;/strong&gt; attribute&lt;/li&gt;
&lt;li&gt;Trying to set a cookie with the &lt;strong&gt;secure&lt;/strong&gt; attribute over &lt;strong&gt;HTTP://&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Too large cookies; keep the cookie size below 4000 bytes for maximum browser compatibility. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cookies-not-included-in-requests&quot;&gt;Cookies not included in requests&lt;/h2&gt;
&lt;p&gt;If you have concluded that the browser has accepted the cookie and it is found under &lt;strong&gt;Application -&gt; Storage&lt;/strong&gt;, great! One step forward! What’s next?&lt;/p&gt;
&lt;p&gt;There are a few reasons why the browser might decide not to include cookies in outgoing requests:&lt;/p&gt;
&lt;p&gt;Some of the reasons for blocking are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Protocol Mismatch&lt;br&gt;
Cookies with the &lt;strong&gt;secure&lt;/strong&gt; attribute will not be included in requests over &lt;strong&gt;HTTP&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incorrect samesite&lt;/strong&gt;&lt;br&gt;
For cross-site requests, you must use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;samesite=none;secure&lt;/strong&gt; when you need to support &lt;strong&gt;GET&lt;/strong&gt; &amp;#x26; &lt;strong&gt;POST&lt;/strong&gt; in requests to another site&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;samesite=lax&lt;/strong&gt; when you only need to support &lt;strong&gt;GET&lt;/strong&gt; requests across sites.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**Cross-Origin Resource Sharing (CORS)&lt;br&gt;
**We won’t go into CORS in this blog post. CORS can be problematic when you do API requests from JavaScript using the XMLHttpRequest or Fetch APIs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Under the &lt;strong&gt;network tab&lt;/strong&gt;, you can see why a cookie was not included in the request, as shown in the example below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Example of how the browser blocks cookies due to the samesite attribute.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;792&quot; height=&quot;180&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-samesite-strict-cookie-blocked-cross-site.BQzwgbj-_Z1hLyzJ.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;common-samesite-cookie-problems&quot;&gt;Common SameSite Cookie Problems&lt;/h2&gt;
&lt;p&gt;Some of the common &lt;strong&gt;samesite&lt;/strong&gt; problems that I see are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SameSite=none cookies&lt;/strong&gt;&lt;br&gt;
Cookies set with the &lt;strong&gt;samesite=none&lt;/strong&gt; attribute must also be marked with the &lt;strong&gt;secure&lt;/strong&gt; attribute. This means that they must always be used over HTTPS, a common problem, for example, when working with OpenID Connect.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Missing samesite header?&lt;/strong&gt;&lt;br&gt;
All cookies that you set should have a &lt;strong&gt;samesite&lt;/strong&gt; attribute. Otherwise, you might end up with unexpected issues where browsers will start to block them in certain circumstances.&lt;/p&gt;
&lt;p&gt;For example, &lt;strong&gt;Firefox&lt;/strong&gt; writes this warning to the console when you try to set a cookie without a valid samesite attribute:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;






&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;em&gt;Cookie “NormalCookie” does not have a proper “SameSite” attribute value. Soon, cookies without the “SameSite” attribute or with an invalid value will be treated as “Lax”. This means that the cookie will no longer be sent in third-party contexts. If your application depends on this cookie being available in such contexts, please add the “SameSite=None“ attribute to it. To know more about the “SameSite“ attribute, read&lt;/em&gt; &lt;a href=&quot;https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite&quot;&gt;&lt;em&gt;https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite&lt;/em&gt;&lt;/a&gt; &lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;/table&gt;
&lt;h2 id=&quot;which-samesite-value-should-you-use&quot;&gt;Which samesite value should you use?&lt;/h2&gt;
&lt;p&gt;Some guidance about what value to use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strict&lt;/strong&gt;&lt;br&gt;
Use this value for cookies that should only be used within the same site. For example, consider using this value for your session cookie. Using strict cookies provides the maximum protection against Cross-Site Requests.&lt;/li&gt;
&lt;li&gt;**Lax&lt;br&gt;
**This is the new default for cookies, which only includes the cookie GET requests across sites.&lt;/li&gt;
&lt;li&gt;**None&lt;br&gt;
**Use this value for cookies that should be included in all site requests. This value must be set together with the &lt;strong&gt;secure&lt;/strong&gt; attribute. This is the least secure option. This makes the cookies behave like they behaved before the &lt;strong&gt;samesite&lt;/strong&gt; concept was introduced. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cookies-in-aspnet-core&quot;&gt;Cookies in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;What can go wrong when we use cookies in ASP.NET Core?&lt;/p&gt;
&lt;p&gt;When you use the cookie authentication handler, the default session cookie is set as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; .AspNetCore.Cookies=xxxxxxx; path=/; secure; samesite=lax; httponly&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a sensible default. You might be tempted to set the session cookie to &lt;strong&gt;samesite=strict&lt;/strong&gt;, as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCookie&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                 &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	   {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;           opt.Cookie.SameSite &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; SameSiteMode.Strict;                 &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;       });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you just use local authentication (like ASP.NET Core Identity), that is usually fine. But, when you use it together with OpenID Connect, you will have problems, as shown in the image below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;OpenID Connect Cookies blocked by the browser&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;768&quot; height=&quot;255&quot; src=&quot;https://nestenius.se/_astro/chrome-devtools-cookies-samesite-strict-blocked-warning.BrEVGz8H_Z1iUjma.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;openid-connect-cookies-and-identityserver&quot;&gt;OpenID Connect, cookies, and IdentityServer&lt;/h2&gt;
&lt;p&gt;When you work with OpenID Connect, plenty of cookies will be involved. For example, if you use &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt;, then the following cookies are involved during authentication:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Overview of the cookies used when authenticating using OpenID Connect&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;419&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-openidconnect-cookie-flow-browser-identityserver.jICGSTCe_Z1VhVTV.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Not properly configuring and understanding these cookies is a common source of problems.&lt;/p&gt;
&lt;h2 id=&quot;same-domain-but-different-ports&quot;&gt;Same domain but different ports&lt;/h2&gt;
&lt;p&gt;You might think that when you have multiple sites on the same domain but on different ports, they would have separate cookie jars:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mydomain.com&quot;&gt;http://www.mydomain.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mydomain.com&quot;&gt;https://www.mydomain.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.mydomain.com:8080&quot;&gt;http://www.mydomain.com:8080&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But it turns out that they will all share the same cookie jar. The cookies are tied to the domain, not the port or scheme.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I hope the steps outlined above can help you troubleshoot your cookie problems. Here are some tips for healthier and more secure cookies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**Always use HTTPS&lt;br&gt;
**Most cookie problems will go away when you move your traffic to HTTPS. Using HTTPS today is also a must If you care about security.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set samesite&lt;/strong&gt;&lt;br&gt;
Always add the correct samesite attribute; this is a must today.&lt;/li&gt;
&lt;li&gt;Add the &lt;strong&gt;secure&lt;/strong&gt; and &lt;strong&gt;HttpOny&lt;/strong&gt; attribute&lt;br&gt;
Securing our cookies and all sensitive cookies should always have these two attributes set.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do research the path and domain attributes&lt;/strong&gt;&lt;br&gt;
They can be used to further restrict to what domain (including sub-domains) and path the cookies should be included on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.chromium.org/updates/same-site/test-debug/&quot;&gt;Tips for testing and debugging SameSite-by-default and “SameSite=None; Secure” cookies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/samesite-cookie-recipes/#use-cases-for-cross-site-or-third-party-cookies&quot;&gt;SameSite cookie recipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/samesite-cookies-explained/&quot;&gt;SameSite cookies explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hacks.mozilla.org/2020/08/changes-to-samesite-cookie-behavior/&quot;&gt;Changes to SameSite Cookie Behavior – A Call to Action for Web Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-handle-samesite-cookie-changes-chrome-browser&quot;&gt;Handle SameSite cookie changes in Chrome browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/samesite/system-web-samesite&quot;&gt;Work with SameSite cookies in ASP.NET&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/same-site-same-origin/&quot;&gt;Understanding “same-site” and “same-origin”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect claim problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/&quot;&gt;Troubleshooting JwtBearer authentication problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web security fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;Let me know if you have any feedback, anything I missed, or any bugs/typos. You can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>BearerToken: The new Authentication handler in ASP.NET Core 8</title><link>https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/</link><guid isPermaLink="true">https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/</guid><description>Microsoft introduced the new BearerToken authentication handler in ASP.NET Core 8 as part of an initiative to streamline and modernize authentication</description><pubDate>Tue, 29 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Microsoft introduced the new &lt;strong&gt;&lt;code&gt;BearerToken&lt;/code&gt;&lt;/strong&gt; authentication handler in ASP.NET Core 8 as part of an initiative to streamline and modernize authentication processes. This blog post dives into how the &lt;strong&gt;&lt;code&gt;BearerToken&lt;/code&gt;&lt;/strong&gt; in ASP.NET Core handler works, its key features, and how it differs from existing authentication handlers like Cookie and JwtBearer.&lt;/p&gt;
&lt;p&gt;Head over to the blog post &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/improvements-auth-identity-aspnetcore-8/&quot;&gt;Improvements to auth and identity in ASP.NET Core 8&lt;/a&gt; to read more about their effort to improve ASP.NET Core authentication.&lt;/p&gt;
&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;
&lt;p&gt;In the past, after you authenticated as a user, your application typically issues a session cookie. This cookie contains your user identity in the form of a ClaimsPrincipal. The content of the cookie is protected and encrypted using the built-in Data Protection API.&lt;/p&gt;
&lt;p&gt;The issuer of this cookie is the Cookie authentication handler. This handler has two main purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; When a user has been authenticated, the handler issues a new session cookie based on the provided user details, as shown in the picture below:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Inside the Cookie authentication handler in ASP.NET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;595&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-cookie-handler-login-set-cookie-response.BUh6-RbK_Z1ngt3T.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; Authenticating all incoming requests by looking for a session cookie. If a valid cookie is found, it will create a ClaimsPrincipal user object based on the information inside the cookie. This user object is then passed along the request pipeline in ASP.NET Core.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;The purpose of the Cookie authentication handler in ASPNET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;241&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-cookie-handler-request-to-claims-principal-flow.C4OwM1iL_YJVOa.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-do-we-need-a-new-authentication-handler&quot;&gt;Why do we need a new authentication handler?&lt;/h2&gt;
&lt;p&gt;Today, systems typically rely on the session cookie to authenticate user requests. This means that you are usually forced to use the user interface as provided by the backend server, with few options for customization. This often results in an inconsistent experience for users when they transition from a browser-based app experience to a server-rendered one.&lt;/p&gt;
&lt;p&gt;Some of the goals by Microsoft are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt; Improve Cookie-Based Authentication: Introduce more customization and consistent user experience between single-page and server-rendered apps.&lt;/li&gt;
&lt;li&gt; Introduce Token-Based Authentication: Shift from cookies to the more flexible and widely-used, token-based authentication system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The BearerToken handler is a core component of this effort.&lt;/p&gt;
&lt;h2 id=&quot;what-does-the-bearertoken-handler-in-aspnet-core-do&quot;&gt;What does the BearerToken handler in ASP.NET Core do?&lt;/h2&gt;
&lt;p&gt;The new BearerToken handler can be seen as an alternative to the cookie handler, but with a twist..&lt;/p&gt;
&lt;p&gt;Instead of issuing cookies, it will issue an access and refresh token. These tokens are not JWT tokens (for accessing external APIs). Instead, they are meant to be used between the client and web applications.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The BearerToken authentication handler&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;307&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-bearer-token-handler-spa-mobile-flow.BTQmscen_1CWfJI.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The BearerToken handler is not meant to be a stand-alone component. Instead, it is meant to be used with the ASP.NET Core Identity or a similar library.&lt;/p&gt;
&lt;h2 id=&quot;sample-aspnet-core-bearertoken-application&quot;&gt;Sample ASP.NET Core BearerToken Application&lt;/h2&gt;
&lt;p&gt;I created a tiny sample application that shows the BearerToken handler in action. You can find the source code on &lt;a href=&quot;https://github.com/tndata/bearertoken-demo-application&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Running the Sample Application Do the following steps to use the application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start a web client, like Postman.&lt;/li&gt;
&lt;li&gt;Start the application and click on the Login link.&lt;/li&gt;
&lt;li&gt;As a result, you will be presented with the access and refresh token in JSON.&lt;/li&gt;
&lt;li&gt;In Postman:
&lt;ul&gt;
&lt;li&gt;Make a GET request to the site homepage at &lt;code&gt;https://localhost:5001&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Choose Auth -&gt; Bearer Token&lt;/li&gt;
&lt;li&gt;Copy/paste the access token from the page.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Press Send to send the request to the site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt=&quot;Sample BearerToken demo application screenshot&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;418&quot; src=&quot;https://nestenius.se/_astro/postman-bearer-token-auth-tab-access-token.s8C6TO1Z_1o0nVU.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;As a result, you should get an HTML page back. Click on the Preview tab to view the page in Postman, as shown below. On the page, you will see the claims extracted from the access token you sent to the application.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Sample content of the beartoken token&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;774&quot; height=&quot;466&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-bearertoken-claims-preview-insomnia.EPUP3emi_ZdUBRw.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-cant-i-run-this-directly-in-the-browser&quot;&gt;Why can’t I run this directly in the browser?&lt;/h2&gt;
&lt;p&gt;The BearerToken handler is meant for clients who prefer to do authentication programmatically instead of going through the usual UI pages. That is why you must use tools like Postman so that you can get the token to be included in the authorization header.&lt;/p&gt;
&lt;p&gt;Feel free to replace the BearerToken handler with the cookie handler in the source code. If you do, it will work as a normal web application using a session cookie.&lt;/p&gt;
&lt;h2 id=&quot;whats-inside-the-tokens-provided-by-the-bearertoken-handler&quot;&gt;What’s inside the tokens provided by the BearerToken handler?&lt;/h2&gt;
&lt;p&gt;We saw that the result of a sign-in is this JSON document:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;token_type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bearer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;access_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;CfDJ8F2Y2KDGq...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;expires_in&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;refresh_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;CfDJ8F2Y2KDG...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;whats-inside-the-access-token-can-we-look-inside-it&quot;&gt;What’s inside the access token? Can we look inside it?&lt;/h2&gt;
&lt;p&gt;By default, it is encrypted using the Data Protection API, but with a little code, we can make a transparent data protector that will not perform any encryption. This allows us to peek inside the tokens.&lt;br&gt;
To do this, we can implement a custom transparent IDataProtector:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IDataProtector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IDataProtector&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; purpose&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Protect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;plaintext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; plaintext;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Unprotect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;byte&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[] &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;protectedData&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; protectedData;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we configure the BearerToken handler to use this custom protector instead:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddBearerToken&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;o&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.BearerTokenProtector &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TicketDataFormat&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                                    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    o.RefreshTokenProtector &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TicketDataFormat&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                                    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyDataProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                                        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;CreateProtector&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when we sign-in again, we get the same JSON structure as before:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;token_type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bearer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;access_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BQAAABdCZWFyZXJUb2tlbjpBY2Nlc3NUb2tlbgEAAAALQmVhcmVyVG9rZW4BAAEABgAAAAEADl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;RvcmUgTmVzdGVuaXVzAQABAAEAAAAAAD1odHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;5L2NsYWltcy9jb3VudHJ5BlN3ZWRlbgEAAQABAAAAAABCaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8w&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;NS9pZGVudGl0eS9jbGFpbXMvZW1haWxhZGRyZXNzD3RvcmVAdG4tZGF0YS5zZQEAAQABAAAAAAAISm9iVGl0bGUWQ29uc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;3VsdGFudCBhbmQgdHJhaW5lcgEAAQABAAAAAAAISm9iTGV2ZWwGU2VuaW9yAQABAAEAAAAAAAd3ZWJwYWdlFmh0dHBzOi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;8vd3d3LnRuLWRhdGEuc2UBAAEAAQAAAAAAAAABAAAAAgAAAAsucGVyc2lzdGVudAAILmV4cGlyZXMdTW9uLCAyMSBBdWc&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;gMjAyMyAxNDo1MTozOSBHTVQ&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;expires_in&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;3600&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;refresh_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BQAAABhCZWFyZXJUb2tlbjpSZWZyZXNoVG9rZW4BAAAAC0JlYXJlclRva2VuAQABAAYAAAABA&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  A5Ub3JlIE5lc3Rlbml1cwEAAQABAAAAAAA9aHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  dGl0eS9jbGFpbXMvY291bnRyeQZTd2VkZW4BAAEAAQAAAAAAQmh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  wMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcw90b3JlQHRuLWRhdGEuc2UBAAEAAQAAAAAACEpvYlRpdG&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  xlFkNvbnN1bHRhbnQgYW5kIHRyYWluZXIBAAEAAQAAAAAACEpvYkxldmVsBlNlbmlvcgEAAQABAAAAAAAHd2VicGFnZ&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  RZodHRwczovL3d3dy50bi1kYXRhLnNlAQABAAEAAAAAAAAAAQAAAAEAAAAILmV4cGlyZXMdTW9uLCAwNCBTZXAgMjAy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  MyAxMzo1MTozOSBHTVQ&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tokens are still a bit scrambled, so this didn’t help us that much!&lt;br&gt;
However, what is the first thing you check when you have a string of random characters? You check if it is base64 encoded, and there are plenty of online tools to do that. One is &lt;a href=&quot;https://www.base64decode.org/&quot;&gt;https://www.base64decode.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If we do that, we can see the data stored inside the &lt;strong&gt;access token:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.....BearerToken:AccessToken.....BearerToken...........Tore Nestenius..........=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country.Sweden..........B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.tore@tn-data.se...........&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;JobTitle.Consultant and trainer...........JobLevel.Senior...........&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;webpage.https://www.tn-data.se......................persistent...expires.Mon, 21 Aug 2023 14:51:39 GMT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Replaced all non-ASCII characters with a .)&lt;/p&gt;
&lt;h2 id=&quot;what-is-inside-the-bearertoken-refresh-token&quot;&gt;&lt;strong&gt;What is inside the BearerToken refresh token?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If you &lt;strong&gt;base64 decode&lt;/strong&gt; the &lt;strong&gt;refresh&lt;/strong&gt; &lt;strong&gt;token&lt;/strong&gt;, you will find:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;.....BearerToken:RefreshToken.....BearerToken...........Tore Nestenius..........=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country.Sweden..........B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.tore@tn-data.se...........&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;JobTitle.Consultant and trainer...........JobLevel.Senior...........&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;webpage.https://www.tn-data.se......................expires.Mon, 04 Sep 2023 13:51:39 GMT&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Comparing the two tokens, you will see they contain the same user information except for the token type and expiration time (Highlighted in bold above). The expiration time can be customized in the BearerToken options.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-the-refresh-token&quot;&gt;&lt;strong&gt;What is the purpose of the refresh token?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;As we see above, the refresh token contains the same user information as the access token. One observation when reviewing the source code for the BearerHandler is that it only issues refresh tokens, but it does not contain any code to handle them. Instead, this token is meant to be consumed by another library (like ASP.NET Core Identity). For more details, see this GitHub issue:&lt;br&gt;
&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/47228&quot;&gt;Add token refresh endpoints to identity&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another observation is that these tokens are not JWT-tokens, which are usually a common format to transfer user information between systems.&lt;/p&gt;
&lt;h2 id=&quot;comparing-the-bearertoken-cookie-and-jwtbearer-handlers&quot;&gt;Comparing the BearerToken, Cookie, and JwtBearer Handlers&lt;/h2&gt;
&lt;p&gt;In ASP.NET Core, we now have three handlers that do almost the same thing. The picture below tries to summarize the differences: &lt;img alt=&quot;Comparing the BearerToken, Cookie, and JwtBearer Handlers&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;758&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-cookie-jwt-bearer-bearertoken-handler-comparison.TKkDrHdH_Z1g9Cps.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;All three aim to authenticate user requests, while two of them also handle user sign-in information.&lt;/p&gt;
&lt;p&gt;What about security? From a security perspective, I have mixed feelings about issuing and storing tokens in the client and if this can be done securely. As we all know, public clients (like mobile and browser-based apps) can’t handle secrets. However, this is all new for us as .NET developers, so we must see what patterns will emerge. Personally, I would probably introduce some BFF-style proxy between the client and server, as excellently explained in these two videos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://youtu.be/lEnbi4KClVw%20&quot;&gt;alert‘OAuth 2 0’; // The impact of XSS on OAuth 2 0 in SPAs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=hWJuX-8Ur2k&quot;&gt;Securing SPAs and Blazor Applications using the BFF (Backend for Frontend) Pattern - Dominick Baier&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;issues-with-the-bearertoken-handler-in-aspnet-core&quot;&gt;Issues with the BearerToken handler in ASP.NET Core&lt;/h2&gt;
&lt;p&gt;Working on this blog post has also raised a few questions, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Token size: The tokens can get quite large when they are filled with user claims, and there is no option to add a SessionStore(ITicketStore) like we can when we use the Cookie handler.&lt;/li&gt;
&lt;li&gt;No way to customize the content of the refresh token: I am a bit curious why the content of it is the same as in the access token. I would assume we need less information in the refresh token.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;I hope you now better understand the purpose of the new BearerToken handler in ASP.NET Core, how it works, and its limitations. This handler is part of ASP.NET Identity, so it will be interesting to see how this all turns out. However, that is a subject for another blog post.&lt;/p&gt;
&lt;h2 id=&quot;what-is-new-in-aspnet-core-9&quot;&gt;What is new in ASP.NET Core 9?&lt;/h2&gt;
&lt;p&gt;ASP.NET Core 9 introduces two significant features aimed at enhancing support for OpenID Connect: Pushed Authorization Requests (PAR) and the AdditionalAuthorizationParameters option. PAR helps improve security and performance by allowing confidential clients to initiate authorization requests directly with the authorization server. The AdditionalAuthorizationParameters option provides flexibility for developers, enabling custom parameters to be passed during authorization. Both features are crucial for developers working with advanced OpenID Connect scenarios. To dive deeper into PAR, check out my blog post: &lt;a href=&quot;https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://devblogs.microsoft.com/dotnet/improvements-auth-identity-aspnetcore-8/&quot;&gt;Improvements to auth and identity in ASP.NET Core 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/47286&quot;&gt;Auth improvements in ASP.NET Core 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.bearertoken?view=aspnetcore-8.0&quot;&gt;Microsoft.AspNetCore.Authentication.BearerToken&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication/BearerToken/src&quot;&gt;BearerToken source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/47228&quot;&gt;Add token refresh endpoints to identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/50009&quot;&gt;MapIdentityApi HTTP endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/47227&quot;&gt;Add API endpoints for generating identity tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/42158&quot;&gt;ASP.NET Core SPA Templates Need Better Options for Authentication code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect claim problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/&quot;&gt;Troubleshooting JwtBearer authentication problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/configuring-asp-net-core-forwarded-headers-middleware/&quot;&gt;Configuring ASP.NET Core Forwarded Headers Middleware&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web security fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Debugging JwtBearer Claim Problems in ASP.NET Core</title><link>https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/</link><guid isPermaLink="true">https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/</guid><description>A common problem when protecting your ASP.NET Core APIs is that expected claims are not found in the user object. In this blog post, I will give you some</description><pubDate>Fri, 02 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common problem when protecting your ASP.NET Core APIs is that expected claims are not found in the user object. In this blog post, I will give you some ideas for how to diagnose these types of problems. If you have claim problems related to the OpenIDConnect handler, head over to the blog post about &lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect Claim Problems in ASP.NET Core&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-the-jwtbearer-handler&quot;&gt;&lt;strong&gt;What is the purpose of the JwtBearer handler?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;We use the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication/JwtBearer/src&quot;&gt;&lt;strong&gt;JwtBearer&lt;/strong&gt;&lt;/a&gt; authentication handler to protect ASP.NET Web APIs. Its primary purpose is to look for an access token in the incoming request, and if one is found, validate it and create a user object of type ClaimsPrincipal. JwtBearer only deals with access tokens, and it is essential to remember that you should never try to send an ID or refresh tokens to APIs.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Ovewview of the core purpose of the JwtBearer handler in ASP.NET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;329&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-jwtbearer-handler-authorization-header-flow.DgUWieJz_ZzRsGK.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;first-problem-are-the-expected-claims-inside-the-received-access-token&quot;&gt;First problem: Are the expected claims inside the received access token?&lt;/h2&gt;
&lt;p&gt;Using a tool like &lt;a href=&quot;https://www.getfiddler.com/&quot;&gt;&lt;strong&gt;Fiddler&lt;/strong&gt;&lt;/a&gt;, you can capture the raw access token from the incoming request and then use a site like &lt;a href=&quot;https://jwt.io/&quot;&gt;&lt;strong&gt;jwt.io&lt;/strong&gt;&lt;/a&gt; to inspect what the access token contains.&lt;/p&gt;
&lt;p&gt;The token is typically included in the Authorization header, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://api.tn-data.se:7001/api/payments HTTP/1.1 &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; api.tn-data.se&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Authorization&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjU1...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the token is not found here, then you have problems with the client that sends the token and how to troubleshoot that is beyond the scope of this blog post.&lt;/p&gt;
&lt;h2 id=&quot;how-can-i-see-the-received-access-token-using-net-code&quot;&gt;How can I see the received access token using .NET code?&lt;/h2&gt;
&lt;p&gt;If you can’t use a tool like Fiddler to capture the access token, then you can for example, add the following event handler to the JwtBearer handler:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    opt.Events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; JwtBearerEvents&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        OnMessageReceived &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; token&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Request.Headers.Authorization.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Request.Path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(token))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;URL: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;Token: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;rn&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;URL: &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Token: No access token providedrn&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Alternatively, you can send the information to the log if writing to the console is not possible.)&lt;/p&gt;
&lt;p&gt;A sample output when using the code above:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Access token URL: /api/payments &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Token: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwODE2RTk1MUU5NEJDMjEzNzEzN0Y0MUNGN0YwRDg3IiwidHlwIjoiYXQrand0In0.eyJpc3MiOiJodHRwczovL2lkZW…&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is important to not include this code in production, as writing to the console can result in a performance hit and writing to the console is an expensive operation.&lt;/p&gt;
&lt;h2 id=&quot;how-can-i-view-the-claims-that-the-jwtbearer-handler-received&quot;&gt;How can I view the claims that the JwtBearer handler received?&lt;/h2&gt;
&lt;p&gt;To see a list of all the received &lt;a href=&quot;https://tn-data.se/openid-connect/#claims&quot;&gt;claims&lt;/a&gt;, you can add the following event handler to JwtBearer:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     opt.Events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; JwtBearerEvents&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;         //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;         OnTokenValidated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;         {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;             Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;             Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Claims from the access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;             if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (ctx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Principal &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;             {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                 foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; claim&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ctx.Principal.Claims)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                     Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                 }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;             }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;             Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;             return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;         }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this code, you should see a list of the claims extracted from the access token written to the console. For example, it could look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims from the access token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iss - https://localhost:6001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;nbf - 1682863284&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iat - 1682863284&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exp - 1682923284&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - payment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - invoice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - order&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - openid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - profile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - employee_info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - api&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/claims/authnmethodsreferences - pwd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;client_id - missingjwtbearer-claims-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth_time - 1682863282&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/identity/claims/identityprovider - local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name - Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;seniority - Senior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;contractor - no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/role - ceo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/role - finance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/role - developer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sid - C3768153BD5988F9B8B56D4A3AD518A1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;jti - 5DB88EE876EF1B449E86930CE747AF3B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you might notice above, some claims have been renamed to what Microsoft thinks the claims should be named.&lt;/p&gt;
&lt;p&gt;You can disable this renaming by setting this flag to false:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    opt.MapInboundClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you set the MapInboundClaims flag to false, then the output in my sample application changes to:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims from the access token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iss - https://localhost:6001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;nbf - 1682863284&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iat - 1682863284&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exp - 1682923284&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - payment&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - invoice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - order&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - openid&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - profile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - employee_info&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope - api&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;amr - pwd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;client_id - missingjwtbearer-claims-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub - 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth_time - 1682863282&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idp - local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name - Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;seniority - Senior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;contractor - no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - ceo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - finance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - developer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sid - C3768153BD5988F9B8B56D4A3AD518A1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;jti - 5DB88EE876EF1B449E86930CE747AF3B&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;upskill-with-me-courses-workshops--training&quot;&gt;Upskill With Me: Courses, Workshops &amp;#x26; Training&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Black and white professional headshot of a man in a suit jacket&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;300&quot; height=&quot;300&quot; src=&quot;https://nestenius.se/_astro/male-author-headshot-black-white.CGydhHa-_1jsOWe.webp&quot; srcset=&quot;&quot;&gt; Want to sharpen your skills and make the most of your professional development program? I offer a range of &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;workshop courses&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href=&quot;https://tn-data.se/coaching/&quot;&gt;coaching&lt;/a&gt;&lt;/strong&gt; services for individuals and teams! Click the links or the button to find out more. If you have any questions, I’m happy to help! &lt;a href=&quot;https://tn-data.se/consulting/&quot;&gt;Find Out More&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-aspnet-core-username-and-roles-properties&quot;&gt;Fixing the ASP.NET Core username and roles properties&lt;/h2&gt;
&lt;p&gt;The next problem is fixing the &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;roles&lt;/strong&gt; of the user. Again, this is a common problem that I see on Stack Overflow.&lt;/p&gt;
&lt;p&gt;To demonstrate this, I have created the following API action method:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Authorize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ActionResult&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Result&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Result&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    result.Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Unknown Name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    result.IsInRoleCEO &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsInRole&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ceo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    result.Claims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Claims.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c.Type &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;+&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; c.Value)&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Ok&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Result&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; bool&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsInRoleCEO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Claims&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Name and IsInRoleCEO does not work. Why?&lt;/p&gt;
&lt;p&gt;The short answer is that Microsoft has a different opinion of what the name of the claims should be when it looks for the name and role.&lt;/p&gt;
&lt;p&gt;By default, it will look for these two claim types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://schemas.microsoft.com/ws/2008/06/identity/claims/name&quot;&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/name&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://schemas.microsoft.com/ws/2008/06/identity/claims/role&quot;&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/role&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are defined in the .NET source code &lt;a href=&quot;https://source.dot.net/#System.Security.Claims/System/Security/Claims/ClaimTypes.cs,1f8b9c14a9b34152&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To verify this, you can add the following code to a controller method, and it will print the name of the role/name claim type that it looks for:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ClaimsIdentity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)User.Identity;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Name claim type: &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user.NameClaimType);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Role claim type: &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; user.RoleClaimType);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the output:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Name claim type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Role claim type: http://schemas.microsoft.com/ws/2008/06/identity/claims/role&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this, we need to set the name of your name/role claim to match what the claims are named inside the token. For example, in my case, the claims are named &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;role&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    opt.TokenValidationParameters.RoleClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    opt.TokenValidationParameters.NameClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the code above in place, the code below should start to work as expected:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;result.Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Identity&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Unknown Name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;result.IsInRoleCEO &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsInRole&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ceo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my example application, they will return:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&quot;name&quot;: &quot;Bob Smith&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&quot;isInRoleCEO&quot;: true,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;…&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;final-aspnet-core-code&quot;&gt;Final ASP.NET Core code:&lt;/h2&gt;
&lt;p&gt;The code for the API startup and sample controller:&lt;/p&gt;
&lt;p&gt;Program class:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JwtBearerDefaults.AuthenticationScheme)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        opt.Authority &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;[MyAuthority]&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        opt.Audience &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;myaudience&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        opt.TokenValidationParameters.RoleClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        opt.TokenValidationParameters.NameClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        opt.MapInboundClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        opt.Events &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; JwtBearerEvents&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            OnMessageReceived &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; token&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Request.Headers.Authorization.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ToString&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                string&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; msg&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Request.Path &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;??&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(token))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;URL: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;Token: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;token&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;rn&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;URL: &quot;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; path);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Token: No access token providedrn&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            ,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            OnTokenValidated &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Claims from the access token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (ctx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.Principal &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;!=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; null&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                    foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; claim&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ctx.Principal.Claims)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;                Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;                return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Payment controller:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Route(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;api/[controller]&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;ApiController&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;public class PaymentsController : ControllerBase&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    [Authorize]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ActionResult&amp;#x3C;Result&gt;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Get()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; (ClaimsIdentity)User.Identity;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        Console.WriteLine(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;Name claim type: &quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; user.NameClaimType);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        Console.WriteLine(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;Role claim type: &quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; user.RoleClaimType);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Result();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        result.Name&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; User?.Identity?.Name&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; &quot;Unknown Name&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        result.IsInRoleCEO&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; User?.IsInRole(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;ceo&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        result.Claims&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; User?.Claims.Select(c&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; c.Type&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; &quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; c.Value)?.ToList();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; (result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;public class Result&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; string?&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Name&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; get;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; set;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    public bool? IsInRoleCEO { &lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;get;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; set;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    public List&amp;#x3C;string&gt;? Claims { &lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;get;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; set;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;claims-diagnostic-endpoint&quot;&gt;Claims diagnostic endpoint&lt;/h2&gt;
&lt;p&gt;It can be useful to create a diagnostic endpoint for debugging purposes that simply returns the claims found in the User object. Here’s a sample endpoint for ASP.NET Core:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Authorize&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;Route(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;/api/tokendiagnostics&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;public IActionResult GetClaims()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; TestResult()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        Name&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; User.Identity?.Name&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; ??&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; &quot;Unknown Name&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;        Claims&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; (from&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; c&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; User.Claims&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; select&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; c.Type&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; &quot;:&quot;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; +&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; c.Value).ToList()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    return new JsonResult(result);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;public class TestResult&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; string?&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; Name&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; {&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; get;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; set;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    public List&amp;#x3C;string&gt;? Claims { &lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;get;&lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt; set;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;I hope the above steps can help you troubleshoot your JwtBearer claim problems. If you have any suggestions for this blog post, feedback, questions, or would like to get in touch, you can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect claim problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/&quot;&gt;Troubleshooting JwtBearer authentication problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/building-asp-net-core-apis/&quot;&gt;Building ASP.NET Core APIs&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/web-security-fundamentals/&quot;&gt;Web security fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Debugging OpenID Connect Claim Problems in ASP.NET Core</title><link>https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/</link><guid isPermaLink="true">https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/</guid><description>Missing claims in the ClaimsPrincipal user object is a frequent problem when using OpenID Connect authentication in ASP.NET Core. In this blog post, we&apos;ll</description><pubDate>Tue, 28 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Missing claims in the &lt;strong&gt;ClaimsPrincipal&lt;/strong&gt; user object is a frequent problem when using &lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;&lt;strong&gt;OpenID Connect&lt;/strong&gt;&lt;/a&gt; authentication in &lt;strong&gt;ASP.NET Core&lt;/strong&gt;. In this blog post, we’ll explore common causes behind these claim issues and provide troubleshooting steps to help you identify and resolve them effectively.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-the-openidconnect-handler&quot;&gt;&lt;strong&gt;What is the purpose of the OpenIDConnect handler?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The handler has two main tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When the user is challenged (requested to log in), it will redirect the user to the authorization provider, for example, &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;The&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;180&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-openid-connect-handler-challenge-flow-diagram.B1Rsw6q0_ZHbKIt.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;2. When the user is returned to the handler, it will request the tokens from the authorization provider using the authorization code and create an authentication ticket. This ticket is typically passed to the cookie handler, setting the session cookie. &lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The relationship between the OpenIDConnect and Cookie handler in ASP.NET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;299&quot; src=&quot;https://nestenius.se/_astro/aspnetcore-oidc-handler-cookie-token-userinfo-flow.dSKOLc0m_2nXoRM.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;does-the-authorization-server-actually-provide-the-claims&quot;&gt;&lt;strong&gt;Does the authorization server actually provide the claims?&lt;/strong&gt; &lt;/h2&gt;
&lt;p&gt;The OpenIDConnect handler will get the claims from two sources:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The ID-Token&lt;/li&gt;
&lt;li&gt;Optionally, from the UserInfo endpoint.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using a tool like &lt;a href=&quot;https://www.getfiddler.com&quot;&gt;Fiddler&lt;/a&gt;, you can capture the ID token and the optional request to the UserInfo endpoint.&lt;/p&gt;
&lt;p&gt;Why does it use two sources? One reason is that it reduces the size of the ID token. The UserInfo endpoint is also helpful because it lets the client get the latest user details when needed. You can read more about the specification for the &lt;a href=&quot;https://openid.net/specs/openid-connect-core-1_0.html#UserInfo&quot;&gt;UserInfo&lt;/a&gt; endpoint &lt;a href=&quot;https://openid.net/specs/openid-connect-core-1_0.html#UserInfo&quot;&gt;here&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;If the expected claims are not found in the ID token, then enabling the &lt;strong&gt;GetClaimsFromUserInfoEndpoint&lt;/strong&gt; flag will tell the OIDC handler to make an additional request to this endpoint.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.GetClaimsFromUserInfoEndpoint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;	..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, when using IdentityServer, the request to this endpoint and its response can look like this:&lt;/p&gt;
&lt;p&gt;Request:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; https://identityservice.secure.nu/connect/userinfo &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Host&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; identityservice.secure.nu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Authorization&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Bearer &amp;#x3C;accesstoken&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;User-Agent&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Microsoft ASP.NET Core OpenIdConnect handler&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Response:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bob Smith&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;given_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;family_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Smith&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;BobSmith@email.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;email_verified&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;website&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;http://bob.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2019-01-02&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Senior&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;no&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;ceo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;finance&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;developer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;sub&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-can-i-see-these-raw-claims-using-code&quot;&gt;&lt;strong&gt;How can I see these raw claims using code?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;One approach is to add this piece of configuration code that sends all the claims received from the &lt;a href=&quot;http://tn-data.se/openid-connect/#id-token&quot;&gt;&lt;strong&gt;ID token&lt;/strong&gt;&lt;/a&gt; and &lt;strong&gt;UserInfo&lt;/strong&gt; endpoint to the console:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.OnUserInformationReceived &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Claims from the ID token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; claim&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ctx.Principal.Claims)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Claims from the UserInfo endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; property&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ctx.User.RootElement.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;EnumerateObject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Name&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;property&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that all claims received are listed above. However, some claims might have been renamed internally.&lt;/p&gt;
&lt;p&gt;In my sample application, I will see the following claims written to the console:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims from the ID token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iss - https://identityservice.secure.nu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;nbf - 1679224115&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;iat - 1679224115&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;exp - 1679224415&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;aud - missingclaims-client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/claims/authnmethodsreferences - pwd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;nonce - 638148209061465255...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;at_hash - htlWY11Ch-wCFfRAlusRRw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sid - 04D122AC27794662F59D55E9D6E85022&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier - 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth_time - 1679224111&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/identity/claims/identityprovider - local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims from the UserInfo endpoint&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name - Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;given_name - Bob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;family_name - Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email - BobSmith@email.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email_verified - True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;website - http://bob.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;employment_start - 2019-01-02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;seniority - Senior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;contractor - no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - [&quot;ceo&quot;,&quot;finance&quot;,&quot;developer&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub - 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you noticed above, some claims have been renamed to what Microsoft thinks the claims should be named internally.&lt;/p&gt;
&lt;p&gt;You can disable this renaming by setting this flag to false:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.MapInboundClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can move to the next step if the expected claims are found above. Otherwise, you have a configuration issue in your authorization server or are asking for the wrong scopes.&lt;/p&gt;
&lt;p&gt;If you’re curious, the actual mapping logic is found in the &lt;strong&gt;JwtSecurityTokenHandler&lt;/strong&gt; class, and the source code for it can be found &lt;a href=&quot;https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;. &lt;/p&gt;
&lt;h2 id=&quot;what-about-the-claims-in-the-access-token&quot;&gt;What about the claims in the access token?&lt;/h2&gt;
&lt;p&gt;The access token is only used to access APIs. This means the handler does not care what is inside the access token. So, we will ignore what it contains. I have written a blog post about &lt;a href=&quot;https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/&quot;&gt;debugging JwtBearer API claim problems&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;openid-connect-handler-and-the-authentication-ticket&quot;&gt;OpenID Connect handler and the authentication ticket&lt;/h2&gt;
&lt;p&gt;You have determined that the claims you are looking for are passed to the OpenIConnect handler as expected.&lt;br&gt;
Great! So, why are the claims not part of the User object? First, let’s explore the Cookie handler.&lt;/p&gt;
&lt;p&gt;When the OpenID Connect handler is done, it will create an authentication ticket and ask the cookie handler to sign in the user using this ticket. The ticket contains the claims and other details about the user, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the OpenIDConnect tells the cookie handler to sign in the user.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;395&quot; height=&quot;494&quot; src=&quot;https://nestenius.se/_astro/openidconnect-handler-authentication-ticket-cookie-handler-d.BHqx_7Aj_Z1ckYXo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;By adding the following event handler to the Cookie handler, we can see what claims it has received:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddCookie&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;cookie&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.Events.OnSigningIn &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ctx&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Claims received by the Cookie handler&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        foreach&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; claim&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ctx.Principal.Claims)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;$&quot;&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Type&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;claim&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; Task.CompletedTask;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my example, I get the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims received by the Cookie handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;amr - pwd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sid - 233D90C7590B9405E6EAFBE8BBF9A167&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub - 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth_time - 1679224198&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idp - local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name - Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;given_name - Bob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;family_name - Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email - BobSmith@email.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would expect something else! So, what is going on here?&lt;/p&gt;
&lt;p&gt;Inside the OpenID Connect handler is a separate ClaimsMapper function that will remove unnecessary claims and only include some specific ones. &lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Where the claims mapping is done in the AddOpenIDConnect handler.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;405&quot; height=&quot;486&quot; src=&quot;https://nestenius.se/_astro/aspnet-core-openidconnect-claims-mapping-authentication-tick.DD_VJe5w_23KDL5.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The default mapping is found in the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectOptions.cs&quot;&gt;OpenIdConnectOptions&lt;/a&gt; class, and it looks like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nonce&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;azp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;acr&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iat&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nbf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;exp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;at_hash&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;c_hash&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ipaddr&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;platf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;DeleteClaim&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ver&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sub&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sub&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;given_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;given_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;family_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;family_name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get the desired claims to be added, we need to explicitly map the ones that we want:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JwtClaimTypes.Role, JwtClaimTypes.Role);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What is the result if we do this?&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims received by the Cookie handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;amr - pwd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;at_hash - nlHaI2Y0sZo6B2rctDULnw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sid - 01616A969D17F7F92EEEDF7B7CA18BEC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub - 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth_time - 1679224524&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idp - local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name - Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;given_name - Bob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;family_name - Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email - BobSmith@email.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email_verified - True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;website - http://bob.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;employment_start - 2019-01-02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;seniority - Senior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;contractor - no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - [&quot;ceo&quot;,&quot;finance&quot;,&quot;developer&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, we can do the following as a shortcut:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapAllExcept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nbf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;exp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nonce&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iat&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;c_hash&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Doing so will result in the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims received by the Cookie handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;amr - pwd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;at_hash - nlHaI2Y0sZo6B2rctDULnw&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sid - 01616A969D17F7F92EEEDF7B7CA18BEC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;sub - 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;auth_time - 1679224524&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;idp - local&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name - Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;given_name - Bob&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;family_name - Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email - BobSmith@email.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;email_verified - True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;website - http://bob.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;employment_start - 2019-01-02&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;seniority - Senior&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;contractor - no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - [&quot;ceo&quot;,&quot;finance&quot;,&quot;developer&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;fixing-the-user-name-and-roles-claims&quot;&gt;&lt;strong&gt;Fixing the user name and roles claims&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The next problem is to fix the User &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;roles.&lt;/strong&gt; Again, this is a common problem on Stack Overflow. &lt;/p&gt;
&lt;p&gt;To get the name of the user, or check if the user is in a given role, you add the following code to any of your action methods:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IActionResult&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Index&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (User.Identity.IsAuthenticated)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User.Identity.Name;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; isCeo&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsInRole&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ceo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;		&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(name);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//null&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(isCeo);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; View&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, let’s fix this! &lt;/p&gt;
&lt;p&gt;One problem is the claims mapping. Microsoft has a different opinion of what the name and role claim should be named, and out of the box, it will just ignore your name and role claims because it thinks they should have a different name.&lt;/p&gt;
&lt;p&gt;By default, it will look for these two claim types:&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;OpenID Connect claim type&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Microsoft claim type&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;role&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://schemas.microsoft.com/ws/2008/06/identity/claims/role&quot;&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/role&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;name&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://schemas.microsoft.com/ws/2008/06/identity/claims/name&quot;&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/name&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;They are defined in the .NET source code &lt;a href=&quot;https://source.dot.net/#System.Security.Claims/System/Security/Claims/ClaimTypes.cs,1f8b9c14a9b34152&quot;&gt;here&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;To verify this, you can run this code, It will print the name of the expected role/name claim, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (User.Identity.IsAuthenticated)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ClaimsIdentity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)User.Identity;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user.NameClaimType);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user.RoleClaimType);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will print:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;http://schemas.microsoft.com/ws/2008/06/identity/claims/role&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this, we need to set the name of your name and role claim as:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.TokenValidationParameters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TokenValidationParameters&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        NameClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RoleClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;role&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we log in again, we will see this code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (User.Identity.IsAuthenticated)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; user&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ClaimsIdentity&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)User.Identity;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user.NameClaimType);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(user.RoleClaimType);    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//role&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; name&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User.Identity.Name;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; isCeo&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; User.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IsInRole&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;ceo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(name);                 &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Console.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;WriteLine&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(isCeo);                &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will print the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;name&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Bob Smith&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;False&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We fixed the name, but the role check still fails. Strange!&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-roles-claim&quot;&gt;Fixing the roles claim&lt;/h2&gt;
&lt;p&gt;To fix the roles, we must first examine the claims provided to the Cookies handler.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims received by the Cookie handler&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;…&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;contractor - no&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;role - [&quot;ceo&quot;,&quot;finance&quot;,&quot;developer&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we see that all of our roles are added as an array to the role claim, and that is now what the IsInRole method expects.&lt;/p&gt;
&lt;h2 id=&quot;what-is-the-problem-here&quot;&gt;What is the problem here? &lt;/h2&gt;
&lt;p&gt;The first problem is this statement:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapAllExcept&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nbf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;exp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;nonce&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;iat&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;c_hash&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will map multiple claims with the same name into an array.&lt;/p&gt;
&lt;p&gt;Let’s remove it and replace it with the mapping we used earlier:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JwtClaimTypes.Role, JwtClaimTypes.Role);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this code will result in the same result. So, this is clearly not a fix!&lt;/p&gt;
&lt;p&gt;The problem is the &lt;strong&gt;MapUniqueJsonKey&lt;/strong&gt; method that will map duplicate claims into an array. So, we should use is the &lt;strong&gt;MapJsonKey&lt;/strong&gt; method instead, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JwtClaimTypes.Role, JwtClaimTypes.Role);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we log in again, we will see the following claims in the Cookies handler:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims received by the Cookie handler … seniority - Senior contractor - no role - ceo role - finance role - developer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, the IsInRole (“ceo”) check will also work!&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;The final code for the OpenID Connect handler is as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddOpenIdConnect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;oidc&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.GetClaimsFromUserInfoEndpoint &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.TokenValidationParameters &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TokenValidationParameters&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        NameClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; JwtClaimTypes.Name,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        RoleClaimType &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; JwtClaimTypes.Role,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapUniqueJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;management&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.ClaimActions.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;MapJsonKey&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JwtClaimTypes.Role, JwtClaimTypes.Role);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.MapInboundClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;&lt;strong&gt;Conclusions&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I hope the steps outlined above can help you troubleshoot your claim problems. If you have any suggestions for this blog post, feedback, questions on this blog post, or would like to get in touch, you can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/&quot;&gt;BearerToken: The new Authentication handler in .NET 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/&quot;&gt;Troubleshooting JwtBearer authentication problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/&quot;&gt;Debugging JwtBearer Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-cookie-problems/&quot;&gt;Debugging cookie problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/&quot;&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker – Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-openid-connect-and-oauth/&quot;&gt;Introduction to OpenID Connect and OAuth&lt;/a&gt;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/building-asp-net-core-apis/&quot;&gt;Building ASP.NET Core APIs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>Troubleshooting JwtBearer authentication issues in ASP.NET Core</title><link>https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/</link><guid isPermaLink="true">https://nestenius.se/net/troubleshooting-jwtbearer-authentication-problems-in-asp-net-core/</guid><description>One of the most frequent questions I encounter on Stack Overflow is how to troubleshoot JwtBearer authentication issues in ASP.NET Core. In this post</description><pubDate>Tue, 21 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of the most frequent questions I encounter on &lt;a href=&quot;https://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt; is how to troubleshoot JwtBearer authentication issues in ASP.NET Core. In this post, I’ll share top tips and solutions to help you resolve common JwtBearer handler problems effectively.&lt;/p&gt;
&lt;h2 id=&quot;the-jwtbearer-handler&quot;&gt;&lt;strong&gt;The JwtBearer handler&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;We add the &lt;strong&gt;JwtBearer&lt;/strong&gt; handler to the authentication middleware in our ASP.NET Core request pipeline, as shown in the picture below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The JwtBearer handler in the ASP.NET Core request pipeline&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;292&quot; src=&quot;https://nestenius.se/_astro/jwtbearer-handler-1024x292.JYVu84qB_24t6d0.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The main purpose of this handler is to look for an access token in the incoming HTTP request and if it is found, create an authenticated &lt;strong&gt;ClaimsPrincipal&lt;/strong&gt; user instance, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;the purpose of the JwtBearer handler in ASP.NET Core&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;465&quot; src=&quot;https://nestenius.se/_astro/authenticated-claims-principal-1024x465.BxfAcW8z_1SNRT1.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;troubleshooting-jwtbearer-authentication-problems&quot;&gt;&lt;strong&gt;Troubleshooting JwtBearer authentication problems&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The first thing to look at is the status code that is returned in the response from your API, as it will guide you to where to start looking for the problem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;401 Unauthorized&lt;/strong&gt;This error is typically generated by the JwtBearer handler, and it means that the incoming token is not found, valid or accepted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;403 Forbidden&lt;/strong&gt;This error is returned if the token is accepted but the user is not authorized to access the API resource. The authorization handler typically generates this error, and a common problem here is that the user object does not contain some of the required claims. Debugging authorization problems are not covered in this blog post.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you get other status codes, this means it is likely that problem is elsewhere. Such as, 400 bad request or 500 server error.&lt;/p&gt;
&lt;h2 id=&quot;verify-the-order-of-the-middleware-modules&quot;&gt;&lt;strong&gt;Verify the order of the middleware modules.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The next thing is to ensure that the order of the authentication and authorization middleware in your request pipeline is correct. You must do authentication before you can do authorization.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;UseAuthorization&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-jwtbearer-www-authenticate-header&quot;&gt;&lt;strong&gt;The JwtBearer WWW-Authenticate header&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If you have verified that the returned status code is &lt;strong&gt;401&lt;/strong&gt;, then the next step is to look at the &lt;strong&gt;WWW-Authenticate&lt;/strong&gt; response header.&lt;/p&gt;
&lt;p&gt;By default, the JwtBearer handler will include a &lt;strong&gt;WWW-Authenticate&lt;/strong&gt; header in the response, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 401&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Unauthorized Date: Sun, 02 Aug 2020 11:19:06 GMT WWW-Authenticate: Bearer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, this header does not say that much, but you can get a more detailed error message by setting the &lt;strong&gt;IncludeErrorDetails&lt;/strong&gt; flag to &lt;strong&gt;true&lt;/strong&gt;, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    opt.IncludeErrorDetails &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; true&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enabling this flag will result in that a more detailed error message in the &lt;strong&gt;WWW-Authenticate&lt;/strong&gt; header, as shown below:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;HTTP&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1.1&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; 401&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; Unauthorized Date: Sun, 02 Aug 2020 11:19:06 GMT WWW-Authenticate: Bearer error=&quot;invalid_token&quot;, error_description=&quot;The signature is invalid&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the time, this can give you a good clue about the source of the problem.&lt;/p&gt;
&lt;p&gt;Don’t forget to make sure this flag is set to false in production, as it might otherwise leak clues to an attacker attacking your API.&lt;/p&gt;
&lt;p&gt;You can read more about the purpose of this header here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate&quot;&gt;WWW-Authenticate response header&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7235&quot;&gt;RFC 7235 - Hypertext Transfer Protocol (HTTP/1.1): Authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;exploring-the-jwtbearer-logs&quot;&gt;&lt;strong&gt;Exploring the JwtBearer logs&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If the previous steps did not identify the problem, then the next step is to look at the log statements generated by the authentication and authorization middleware in ASP.NET Core.&lt;/p&gt;
&lt;p&gt;If you manage your logging in &lt;strong&gt;appsettings.json&lt;/strong&gt; files, then you can add and lower the logging levels to &lt;strong&gt;debug&lt;/strong&gt; or &lt;strong&gt;trace&lt;/strong&gt;, like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;Logging&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;LogLevel&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Default&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Information&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Microsoft&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Warning&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Microsoft.Hosting.Lifetime&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;Information&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Microsoft.AspNetCore.Authentication&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;Microsoft.AspNetCore.Authorization&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;debug&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-7.0&quot;&gt;this&lt;/a&gt; link for more details about how logging works in ASP.NET Core.&lt;/p&gt;
&lt;p&gt;If I don’t have any logging infrastructure ready, I typically use &lt;a href=&quot;https://serilog.net/&quot;&gt;Serilog&lt;/a&gt; and send all my log entries to &lt;a href=&quot;https://datalust.co/seq&quot;&gt;Seq&lt;/a&gt; for further analysis.&lt;/p&gt;
&lt;h2 id=&quot;jwtbearer-claim-problems&quot;&gt;&lt;strong&gt;JwtBearer Claim problems&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If you have problems with your User or Claims, then head over to my blog post &lt;a href=&quot;https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/&quot;&gt;Debugging JwtBearer Claim Problems in ASP.NET Core&lt;/a&gt; &lt;/p&gt;
&lt;h2 id=&quot;conclusions&quot;&gt;&lt;strong&gt;Conclusions&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I hope the steps outlined above can help you troubleshoot your APIS and If you have any suggestions for this blog post, feedback, or questions on this blog post or would like to get in touch, you can find my contact details &lt;a href=&quot;https://nestenius.se/contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/bearertoken-the-new-authentication-handler-in-net-8/&quot;&gt;BearerToken: The new Authentication handler in .NET 8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/debugging-jwtbearer-claim-problems-in-asp-net-core/&quot;&gt;Debugging JwtBearer Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect claim problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/&quot;&gt;IdentityServer – IdentityResource vs. ApiResource vs. ApiScope&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops &lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/building-asp-net-core-apis/&quot;&gt;Building ASP.NET Core APIs&lt;/a&gt; &lt;img alt=&quot;Building ASP.NET Core APIs - new training workshop&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;44&quot; height=&quot;16&quot; src=&quot;https://nestenius.se/_astro/new-badge-yellow-label.BuswNKOb_Z8ur7o.webp&quot; srcset=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category><category>jwtbearer</category><category>openid-connect</category></item><item><title>IdentityServer - IdentityResource vs. ApiResource vs. ApiScope</title><link>https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/</link><guid isPermaLink="true">https://nestenius.se/net/identityserver-identityresource-vs-apiresource-vs-apiscope/</guid><description>Understanding the differences between IdentityResource, ApiResource, and ApiScope in Duende IdentityServer is a common question among developers, often</description><pubDate>Thu, 02 Feb 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Understanding the differences between &lt;strong&gt;IdentityResource&lt;/strong&gt;, &lt;strong&gt;ApiResource&lt;/strong&gt;, and &lt;strong&gt;ApiScope&lt;/strong&gt; in &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;Duende IdentityServer&lt;/a&gt; is a common question among developers, often seen on platforms like Stack Overflow.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;https://stackoverflow.com/questions/63811157/apiresource-vs-apiscope-vs-identityresource/63812939#63812939&quot;&gt;answer&lt;/a&gt; to this question has gained significant traction as one of my most upvoted responses. In this blog post, I’ll dive deeper into these resource types, explaining what they are, how they function, and their impact on your IdentityServer setup.&lt;/p&gt;
&lt;h2 id=&quot;back-to-basics&quot;&gt;&lt;strong&gt;Back to basics&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In IdentityServer, there are three types of resources&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IdentityResources&lt;/li&gt;
&lt;li&gt;ApiScopes&lt;/li&gt;
&lt;li&gt;ApiResources&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How are these related, and what do they control?&lt;/p&gt;
&lt;p&gt;Before we can answer this, we need to take one step back and talk about &lt;strong&gt;scopes&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;scopes-in-openid-connect&quot;&gt;**Scopes in OpenID Connect&lt;/h2&gt;
&lt;p&gt;**&lt;/p&gt;
&lt;p&gt;When the client application authenticates, it will ask Duende IdentityServer for a set of scopes, as the picture below shows:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;user authenticating to identityserver with a set of scopes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;244&quot; src=&quot;https://nestenius.se/_astro/client-sending-openid-scopes-to-duende-identityserver.CiK9-q-l_1nLM23.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The list of scopes represents a mixed list of what the client wants to get back. So far, so good!&lt;/p&gt;
&lt;p&gt;As the picture shows above, we can divide the list of scopes into two categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;**Identity scopes&lt;br&gt;
**Scopes that are all about what information the client wants to know about the user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;**Access scopes&lt;br&gt;
**Scopes that represent what the client wants to have access to.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These two categories control what goes into the &lt;strong&gt;id&lt;/strong&gt; and &lt;strong&gt;access&lt;/strong&gt; token.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the scopes controls what goes into the id and access tokens&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;196&quot; src=&quot;https://nestenius.se/_astro/identityserver-scopes-id-token-access-token-flow.CMLl5Rl7_Z1E64bi.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;identity-scopes&quot;&gt;&lt;strong&gt;Identity Scopes&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The Identity scopes control what goes into the ID token (or is available from the UserInfo endpoint). In IdentityServer, we define these scopes using &lt;strong&gt;IdentityResources&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The three identity scopes in our example above can, in code, be defined as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;_identityResources &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;IdentityResource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IdentityResources&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;OpenId&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IdentityResources&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;Email&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    employeeInfoScope&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; employeeInfoScope&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IdentityResource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;employee_info&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DisplayName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Employee information&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Description &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Employee information including seniority and status...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we define that clients can ask for the standardized &lt;strong&gt;openid&lt;/strong&gt; and &lt;strong&gt;email&lt;/strong&gt; scope and a custom scope named &lt;strong&gt;employee_info&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;employee_info&lt;/strong&gt; scope, as defined above, is pretty useless. We also typically want to add what &lt;strong&gt;claims&lt;/strong&gt; this scope represents.&lt;/p&gt;
&lt;p&gt;The following picture shows how the identity resources are connected to a set of claims:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the requested scopes are mapped to IdentityResources, APIScopes and requested claims&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;431&quot; src=&quot;https://nestenius.se/_astro/duende-identityserver-identity-resources-vs-api-scopes-claim.CbG5Brue_1n7LFf.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The list of requested claims represents what claims will end up in the &lt;strong&gt;ID token&lt;/strong&gt;. Don’t forget that only the claims found in the user database will be included.&lt;/p&gt;
&lt;p&gt;In code, this means that we will add a list of user claims to our custom scope:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; employeeInfoScope&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IdentityResource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;employee_info&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    DisplayName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Employee information&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Description &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Employee information including seniority and status...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    UserClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;employment_start&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;seniority&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;contractor&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;access-scopes&quot;&gt;&lt;strong&gt;Access scopes&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The access scopes control what &lt;strong&gt;APIs&lt;/strong&gt; and &lt;strong&gt;services&lt;/strong&gt; the client application wants to access and what should go into the &lt;strong&gt;access token&lt;/strong&gt;. In IdentityServer, we define these scopes using &lt;strong&gt;ApiScopes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In code, we can define them as shown in this example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;_apiScopes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApiScope&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiScope&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;payment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        DisplayName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Payments access&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Description &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Access to the payment related services.&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        UserClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;            //These claims will be added to the access token, not the ID-token!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;bonuslevel&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;sendpayments&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiScope&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;invoice&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        DisplayName &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;Invoices access&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        UserClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;approveinvoices&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;            &quot;sendinvoices&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image below shows how the &lt;strong&gt;APIScopes&lt;/strong&gt; and &lt;strong&gt;user claims&lt;/strong&gt; are related based on the code above:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The image below shows how the API Scopes and user claims are related based on the code above:&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;435&quot; src=&quot;https://nestenius.se/_astro/duende-identityserver-identity-resources-vs-api-scopes-cla-2.D40UAQxg_Zl1Y4i.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The requested claims will be added to the &lt;strong&gt;access token&lt;/strong&gt;, not the id token. These claims are useful when, for example, services and APIs need to authorize received access tokens.&lt;/p&gt;
&lt;p&gt;Also, it is important to note that the requested scopes (&lt;strong&gt;IdentityResources&lt;/strong&gt; and &lt;strong&gt;ApiScopes&lt;/strong&gt;) are what the user will give consent to during authentication:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;How the requested scopes are mapped to IdentityResources and APIScopes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;449&quot; src=&quot;https://nestenius.se/_astro/duende-identityserver-identity-resources-vs-api-scopes-diagr.Cjlc-asK_1NRSJo.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;api-resources-in-identityserver&quot;&gt;**API Resources in IdentityServer&lt;/h2&gt;
&lt;p&gt;**&lt;/p&gt;
&lt;p&gt;Defining &lt;strong&gt;IdentityResources&lt;/strong&gt; and &lt;strong&gt;ApiScopes&lt;/strong&gt; is a good start, but we can improve our setup by introducing &lt;strong&gt;API Resources&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;One problem with the current setup is that the &lt;strong&gt;aud&lt;/strong&gt; claim inside the &lt;strong&gt;access token&lt;/strong&gt; contains a generic value:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identity.secure.nu:6001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identity.secure.nu:6001/resources&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;openid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;employee_info&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;payment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;invoice&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we set the &lt;strong&gt;EmitStaticAudienceClaim&lt;/strong&gt; setting to false in IdentityServer:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddIdentityServer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    options.EmitStaticAudienceClaim &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the &lt;strong&gt;aud&lt;/strong&gt; claim is not even included:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identity.secure.nu:6001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;openid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;employee_info&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;payment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;  &quot;invoice&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The access tokens above are a bit too generic, and we often want a more “targeted” access token. That allows the intended service receiving the token to verify that the service is the intended target for the token.&lt;/p&gt;
&lt;h2 id=&quot;introducing-identityserver-api-resources&quot;&gt;&lt;strong&gt;Introducing IdentityServer API Resources&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;By introducing &lt;strong&gt;APIResources&lt;/strong&gt;, we can control what goes into the &lt;strong&gt;audience (aud)&lt;/strong&gt; claim and further improve our setup. For example, we can define two &lt;strong&gt;ApiResources&lt;/strong&gt; as follows:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; paymentApi&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiResource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;paymentapi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Scopes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;payment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    UserClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        //Custom user claims that should be provided when requesting access to this API.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;        //These claims will be added to the access token, not the ID token!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;contractor&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; invoiceApi&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ApiResource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Name &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &quot;invoiceapi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Scopes &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;invoice&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;_apiResources &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;ApiResource&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    paymentApi,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    invoiceApi,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image below shows the relationship between the &lt;strong&gt;ApiScopes&lt;/strong&gt; and &lt;strong&gt;ApiResources&lt;/strong&gt; as defined in the code above:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;the relationship between the ApiScopes and ApiResources in Duende IdentityServer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;486&quot; src=&quot;https://nestenius.se/_astro/duende-identityserver-identity-resources-api-scopes-api-reso.C467-cYV_ZHtTyB.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;When adding these two &lt;strong&gt;APIResources&lt;/strong&gt;, the &lt;strong&gt;access token&lt;/strong&gt; will now contain the following:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://identity.secure.nu:6001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;paymentapi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;invoiceapi&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;scope&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;openid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;employee_info&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;payment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;invoice&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;contractor&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;no&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;employee&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;approveinvoices&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;no&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;sendinvoices&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;sendpayments&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;bonuslevel&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API can verify if it is the intended target by checking the &lt;strong&gt;aud&lt;/strong&gt; claim inside the received access tokens. In addition, the &lt;strong&gt;use claims&lt;/strong&gt; defined in the &lt;strong&gt;ApiScopes&lt;/strong&gt; and &lt;strong&gt;ApiResources&lt;/strong&gt; are also included in the access token.&lt;/p&gt;
&lt;p&gt;As described in the Duende IdentityServer &lt;a href=&quot;https://docs.duendesoftware.com/identityserver/fundamentals/resources/api-resources/&quot;&gt;documentation&lt;/a&gt;, adding &lt;strong&gt;ApiResources&lt;/strong&gt; gives you these additional benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;support for the JWT aud claim. The value(s) of the audience claim will be the name of the ApiResource(s)&lt;/li&gt;
&lt;li&gt;support for adding common user claims across all contained scopes&lt;/li&gt;
&lt;li&gt;support for introspection by assigning an API secret to the resource&lt;/li&gt;
&lt;li&gt;support for configuring the access token signing algorithm for the resource&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/openid-connect/&quot;&gt;OpenID Connect for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/identityserver/v7/fundamentals/resources/&quot;&gt;IdentityServer resource documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/identityserver/v7/fundamentals/resources/api_scopes#authorization-based-on-scopes&quot;&gt;Authorization based on Scopes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.duendesoftware.com/identityserver/v7/fundamentals/resources/api_resources/&quot;&gt;API Resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/63811157/apiresource-vs-apiscope-vs-identityresource/63812939#63812939&quot;&gt;ApiResource vs ApiScope vs IdentityResource on Stack Overflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect claim problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/debugging-jwtbearer-claim-problems-in-asp-net-core/&quot;&gt;Debugging JwtBearer Claim Problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/improving-asp-net-core-security-by-putting-your-cookies-on-a-diet/&quot;&gt;Improving ASP.NET Core Security By Putting Your Cookies On A Diet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/identityserver-in-docker-containers-part-1/&quot;&gt;IdentityServer in Docker – Part 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/introduction-to-identityserver-and-openid-connect/&quot;&gt;Introduction to IdentityServer and OpenID-Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/authentication-and-authorization-in-aspnet-core/&quot;&gt;Authentication and Authorization in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;feedback-comments-found-any-bugs&quot;&gt;Feedback, comments, found any bugs?&lt;/h2&gt;
&lt;p&gt;If you have any feedback or questions on this blog post or would like to get in touch, you can find my contact details &lt;a href=&quot;//contact/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>identityserver</category></item><item><title>I was interviewed by RetroRGB</title><link>https://nestenius.se/hardware/i-was-interviewed-by-retrorgb/</link><guid isPermaLink="true">https://nestenius.se/hardware/i-was-interviewed-by-retrorgb/</guid><description>After my blog post about my Sega Mega Drive project, RetroRGB contacted me and wanted to interview me on their YouTube channel.</description><pubDate>Wed, 23 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After my blog post about my &lt;a href=&quot;https://nestenius.se/hardware/how-i-built-my-own-sega-mega-drive-hardware-dev-kit-from-scratch/&quot;&gt;Sega Mega Drive project&lt;/a&gt;, &lt;a href=&quot;https://www.retrorgb.com/&quot;&gt;RetroRGB&lt;/a&gt; contacted me and wanted to interview me on their YouTube channel.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;RetroRGB YouTube thumbnail for an interview with Tore Nestinius, showing a modded Sega Mega Drive PCB&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;658&quot; height=&quot;369&quot; src=&quot;https://nestenius.se/_astro/retrorgb-youtube-interview-tore-nestinius-mega-drive-pcb.B1E32WE0_1j3h47.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;This was my first major podcast/interview and was a very fun experience. You can view the interview &lt;a href=&quot;https://www.retrorgb.com/interview-with-tore-nestenius.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>hardware</category></item><item><title>ASP.NET Core 6 - JwtBearer library: what’s new?</title><link>https://nestenius.se/net/asp-net-core-jwtbearer-library-whats-new/</link><guid isPermaLink="true">https://nestenius.se/net/asp-net-core-jwtbearer-library-whats-new/</guid><description>As a developer and trainer, it is hard to keep up with all the changes in all the libraries. In this blog post, I will summarize the recent key changes</description><pubDate>Fri, 04 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a developer and trainer, it is hard to keep up with all the changes in all the libraries. In this blog post, I will summarize the recent key changes that I have found in the ASP.NET Core JwtBearer library versions 3.1.22, 5.0.13, and 6.0.1.&lt;/p&gt;
&lt;p&gt;The JwtBearer library is distributed in the &lt;a href=&quot;https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer/&quot;&gt;Microsoft.AspNetCore.Authentication.JwtBearer&lt;/a&gt; NuGet package. The corresponding source code can be found in the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication&quot;&gt;/src/Security/Authentication/&lt;/a&gt; folder in the &lt;a href=&quot;https://github.com/dotnet/aspnetcore&quot;&gt;ASP.NET Core repository&lt;/a&gt; on GitHub. &lt;/p&gt;
&lt;h2 id=&quot;what-is-the-purpose-of-the-aspnet-core-jwtbearer-library&quot;&gt;What is the purpose of the ASP.NET Core JwtBearer library?&lt;/h2&gt;
&lt;p&gt;The library is implemented as an ASP.NET Core request pipeline middleware, and its sole purpose is to authenticate incoming requests. It will look for JW bearer tokens in the request and, if a token is found, validate it and create a ClaimsPrincipal User instance. The authorization handler can then authorize the user to get access to the requested resource.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing JwtBearer Handler authenticating an HTTP request with Bearer token and creating a ClaimsPrincipal User&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;231&quot; src=&quot;https://nestenius.se/_astro/asp-net-core-jwtbearer-handler-authentication-flow.CnZWbTLS_Zj6bGE.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;&quot;&gt;&lt;/h2&gt;
&lt;p&gt;What runtime does each version target?&lt;/p&gt;
&lt;p&gt;The different versions of the JwtBearer NuGet package target different runtimes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Version 3.x.x targets .NET Core 3.1&lt;/li&gt;
&lt;li&gt;Version 5.x.x targets .NET 5.x&lt;/li&gt;
&lt;li&gt;Version 6.x.x targets .NET 6.x&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That sounds obvious and logical, but the annoying thing with Visual Studio (as of today) is that, even if you have a .NET Core 3.1 application, it still tells you that version 6 is available, even though it should know that version 6.x.x is not compatible with your current project:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Visual Studio NuGet package manager showing Microsoft.AspNetCore.Authentication.JwtBearer update from 3.1.22 to 6.0.1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;794&quot; height=&quot;232&quot; src=&quot;https://nestenius.se/_astro/visual-studio-nuget-jwtbearer-update-3-1-22-to-6-0-1.0D1XOIGw_2rhzyw.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;As a good developer, you want to keep your packages up to date, but trying to update it will result in the following error:&lt;/p&gt;
&lt;p&gt;&lt;img __ASTRO_IMAGE_=&quot;{&amp;#x22;src&amp;#x22;:&amp;#x22;images/reeol0NvNIiW1CZZzz5tIUnNbczqxd0Cco5JYKeeD4m-Q04J0r_EDqLW6oHARg_l5p0-p4mFb32HJ6U5oEY2ivdUcN5GJvekOt0otjeAxcWF3_TzCqpuckhjo52NlSJMYn0QJODI&amp;#x22;,&amp;#x22;alt&amp;#x22;:&amp;#x22;NuGet package update error when trying to install an incompatible JwtBearer version&amp;#x22;,&amp;#x22;index&amp;#x22;:0}&quot;&gt;&lt;/p&gt;
&lt;p&gt;This sure is a bit annoying 🙄&lt;/p&gt;
&lt;h2 id=&quot;comparing-the-editions-of-jwtbearer&quot;&gt;Comparing the editions of JwtBearer&lt;/h2&gt;
&lt;p&gt;To do the research, I really like to get down to the true source, and the best source is of course the ASP.NET source code. So I downloaded it for the three different versions and then compared the result using &lt;a href=&quot;https://www.araxis.com/merge/&quot;&gt;Araxis merge&lt;/a&gt;. I could have done the same comparisons using &lt;a href=&quot;https://www.ndepend.com/&quot;&gt;NDepend&lt;/a&gt;, but I chose to use Araxis merge because it gave me the best overview for this blog post. Using Araxis merge I can compare all three versions at the same time as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Araxis Merge three-way folder and file diff comparing JwtBearerHandler across ASP.NET Core 3.1, 5.0 and 6.0&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;560&quot; src=&quot;https://nestenius.se/_astro/araxis-merge-jwtbearer-handler-three-way-diff.BrEc7DZ4_Z1v1aWh.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The interesting source code for the authentication handlers in ASP.Net Core is found in the &lt;a href=&quot;https://github.com/dotnet/aspnetcore/tree/main/src/Security/Authentication&quot;&gt;/src/Security/Authentication/&lt;/a&gt; directory on GitHub.&lt;/p&gt;
&lt;h2 id=&quot;what-has-changed-between-the-revisions-of-jwtbearer&quot;&gt;What has changed between the revisions of JwtBearer?&lt;/h2&gt;
&lt;p&gt;There are, of course, plenty of small changes in this library, and many of the changes consist of added XML comments and improved support for non-nullable reference types.  &lt;/p&gt;
&lt;p&gt;But let’s look at the ones that actually matter for us using this library:&lt;/p&gt;
&lt;h3 id=&quot;map-inbound-claims-option&quot;&gt;&lt;strong&gt;Map inbound claims option&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;This feature, which was introduced in version 5, allows us to very easily disable one of the most annoying features in the .NET authentication stack, and that is the automatic renaming of some of the claims found in the token.&lt;/p&gt;
&lt;p&gt;The flag is by default set to true, but can now easily be disabled using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{    options.MapInboundClaims &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;    &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//...});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s compare the result when this flag is false or true. If we send the following &lt;strong&gt;access token&lt;/strong&gt; in a request to the library:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;nbf&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1642960856&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;exp&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1642964456&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;iss&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://localhost:6001&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;aud&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;paymentapi&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;client_id&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;client&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;managment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;yes&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;tn@tn-data.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;tore nestenius&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;developer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;support&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ],  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;website&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://www.tn-data.se&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;jti&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;9320D007CFA6EF1C21ACA09908216BC7&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;iat&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1642960856&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;	&quot;scope&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [ &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;payment&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; ]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the resulting &lt;strong&gt;ClaimsPrincipal&lt;/strong&gt; User will contain the following set of claims:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MapInboundClaims = false&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - nbf=1642960856&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - exp=1642964456&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - iss=https://localhost:6001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - aud=paymentapi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - client_id=client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - managment=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - email=tn@tn-data.se&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - name=tore nestenius&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - role=admin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - role=developer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - role=support&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - website=https://tn-data.se&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - iat=1642960856&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - scope=payment&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;MapInboundClaims = true&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;text&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Claims&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - nbf=1642961012&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - exp=1642964612&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - iss=https://localhost:6001 &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - aud=paymentapi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - client_id=client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - managment=yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress=tn@tn-data.se&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - name=tore nestenius&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - http://schemas.microsoft.com/ws/2008/06/identity/claims/role=admin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - http://schemas.microsoft.com/ws/2008/06/identity/claims/role=developer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - http://schemas.microsoft.com/ws/2008/06/identity/claims/role=support&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage=https://tn-data.se&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - iat=1642961012&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; - scope=payment&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have answered questions about claims mapping on &lt;a href=&quot;https://stackoverflow.com/users/68490/tore-nestenius&quot;&gt;Stack Overflow&lt;/a&gt; for quite some time now and I wish this flag was set to false by default. This would have made life so much easier for developers working with claims.&lt;/p&gt;
&lt;h3 id=&quot;customizing-the-jwtbearer-back-channel-httpclient&quot;&gt;&lt;strong&gt;Customizing the JwtBearer back channel HttpClient&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The JwtBearer will, via the Configuration Manager, make HTTP(s) requests to our identity provider through the back channel. For example, it will use this channel to download the &lt;strong&gt;openid-configuration&lt;/strong&gt; and the &lt;strong&gt;public keys&lt;/strong&gt; from the authorization provider (for example &lt;a href=&quot;https://duendesoftware.com/&quot;&gt;IdentityServer&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing JwtBearer Handler with ConfigurationManager using HttpClient and Backchannel HttpHandler to fetch OpenID conf&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1173&quot; height=&quot;342&quot; src=&quot;https://nestenius.se/_astro/jwtbearer-handler-configuration-manager-backchannel-identity.BFhiEWqR_Zt7Nui.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Before version 6, we could provide our own &lt;strong&gt;HttpMessageHandler&lt;/strong&gt; to customize the BackChannel request and response, by for example:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(JwtBearerDefaults.AuthenticationScheme)        .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;opt&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{   opt.BackchannelHttpHandler &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; MyBackChannelListener&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;();   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//...};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Why would you want to provide your own? Some of the use-cases can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improve logging of the backchannel requests&lt;/li&gt;
&lt;li&gt;Add custom headers to the outgoing request&lt;/li&gt;
&lt;li&gt;Inspect the response&lt;/li&gt;
&lt;li&gt;Implement automatic retry when a request fails, perhaps using the &lt;a href=&quot;https://github.com/App-vNext/Polly&quot;&gt;Polly Project library&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In version 6 they also introduced the option to provide your own &lt;strong&gt;HttpClient&lt;/strong&gt; instance. This was added as a response to GitHub &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/27426&quot;&gt;issue #27426&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This allows us to provide our own client like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;builder.Services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()       .&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{      options.Backchannel &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; HttpClient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;()        {              &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//Custom client options        };});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why would you want to provide your own client?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Work with the &lt;strong&gt;HttpClientFactory&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Better control of the &lt;strong&gt;HTTP protocol&lt;/strong&gt; to be used&lt;br&gt;
(i.e., only use HTTP/2 or HTTP/1.1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some related resources&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/10542&quot;&gt;Use IHttpClientFactory in RemoteAuthenticationOption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dotnet/aspnetcore/pull/27572&quot;&gt;Add HttpClient Backchannel property to JwtBearerOptions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;customizing-the-configuration-refresh-intervals&quot;&gt;&lt;strong&gt;Customizing the configuration refresh intervals&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In .NET 5 they introduced two new options to control the backchannel refresh intervals and you define them in code using:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     //.NET 5 defaults        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	 options.AutomaticRefreshInterval &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TimeSpan&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);   &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//24 hours        &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	 options.RefreshInterval &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TimeSpan&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;30&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);           &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//30 seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;     //.NET 6 defaults&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;     options.AutomaticRefreshInterval &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TimeSpan&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;12&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);  &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//12 hours      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	 options.RefreshInterval &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; TimeSpan&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;5&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);            &lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//5 minutes   &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In .NET 6 they changed the default values as shown above, but the main question is of course, what do they control?&lt;/p&gt;
&lt;h3 id=&quot;automatic-refresh-interval&quot;&gt;&lt;strong&gt;Automatic refresh interval&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;This parameter will control how often it will download and refresh the cached &lt;strong&gt;openid-configuration&lt;/strong&gt; and the &lt;strong&gt;JWKS&lt;/strong&gt; signing keys. &lt;/p&gt;
&lt;h3 id=&quot;refresh-interval&quot;&gt;&lt;strong&gt;Refresh interval&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If a back channel request to the identity provider fails, then this value will control the wait time between retries. If it fails to refresh the configuration, then it will keep using the existing configuration that was retrieved earlier.&lt;/p&gt;
&lt;h2 id=&quot;jwtbearer-dependencies&quot;&gt;JwtBearer Dependencies&lt;/h2&gt;
&lt;p&gt;The JwtBearer also depends on a few other libraries, which are:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Diagram showing JwtBearer dependency on Microsoft.IdentityModel.Protocols.OpenIdConnect, which depends on Microsoft.IdentityM&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1024&quot; height=&quot;230&quot; src=&quot;https://nestenius.se/_astro/jwtbearer-identitymodel-openidconnect-dependency-diagram.BiLnw5Uh_Z1P8usz.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;These three libraries are all located in a different repository named &lt;a href=&quot;https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet&quot;&gt;azure-activedirectory-identitymodel-extensions-for-dotnet&lt;/a&gt;. It is not located under the ASP.NET Core, instead it is located under the
&lt;a href=&quot;https://github.com/AzureAD&quot;&gt;AzureAD&lt;/a&gt; organization. &lt;/p&gt;
&lt;p&gt;The versioning of these packages does not follow the same versioning pattern as ASP.NET Core does, so in my investigation, I used the following versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ASP.NET Core 3.1 -&gt; JwtBearer (3.1.22) -&gt; …OpenIDConnect (5.5.0)&lt;/li&gt;
&lt;li&gt;ASP.NET 5.0-&gt; JwtBearer (5.0.13) -&gt; …OpenIDConnect (6.7.1)&lt;/li&gt;
&lt;li&gt;ASP.NET 6.0 -&gt; JwtBearer (6.0.1) -&gt; …OpenIDConnect (6.10.0)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Naturally there have been numerous improvements here too, but for those of us who are working with APIs, the &lt;strong&gt;TokenValidationParameters&lt;/strong&gt; is the most interesting one for us.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;csharp&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;services.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddAuthentication&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;().&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;AddJwtBearer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;    //For example      &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;	options.TokenValidationParameters.ValidateIssuer &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; false&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;    ..&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}    &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What interesting changes are here? If we compare version 5.5.0 with 6.10.0, we see that some of the most interesting changes are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4 id=&quot;new-validation-delegates&quot;&gt;New validation delegates&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AlgorithmValidator&lt;/strong&gt;&lt;br&gt;
By providing a custom delegate to this parameter, you can do your own validation of the cryptographic algorithm used. If provided, then the valid algorithms provided in the ValidAlgorithms parameter will be ignored. Discussions about this delegate can be found &lt;a href=&quot;https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/1413&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeValidator&lt;/strong&gt;By providing a custom delegate to this parameter, you can do your own token type validation instead of the built-in default one. Discussions about this delegate can be found in &lt;a href=&quot;https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1378&quot;&gt;Issue 1378&lt;/a&gt; and &lt;a href=&quot;https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/1385&quot;&gt;Issue #1385&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4 id=&quot;new-properties&quot;&gt;New properties&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IgnoreTrailingSlashWhenValidatingAudience&lt;/strong&gt;&lt;br&gt;
Setting this property will control if a ’/’ is significant at the end of the audience or not. (default true)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TryAllIssuerSigningKeys&lt;/strong&gt;Will control whether all the signing keys should be tried during signature validation when the referred key in the token is not found. (default true)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ValidAlgorithms&lt;/strong&gt;&lt;br&gt;
Contains a list of the valid algorithms for cryptographic operations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ValidTypes&lt;/strong&gt;&lt;br&gt;
Contains a list of the valid token types.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Keeping up with all the changes in the .NET stack is hard and using &lt;a href=&quot;https://www.araxis.com/merge/&quot;&gt;Araxis merge&lt;/a&gt; allowed me to quickly compare three versions of the code base at the same time. The most annoying thing when dealing with the authentication stack is that the code involved is located in two different repositories and it is really annoying that the code in the AzureAD repository is not covered by &lt;a href=&quot;https://source.dot.net/&quot;&gt;https://source.dot.net/&lt;/a&gt;, which otherwise is very handy when searching the Microsoft code base. &lt;/p&gt;
&lt;p&gt;When I create my training materials I often do dive deep into the .NET source code, and looking under the hood gives you another perspective on how things actually work and the amazing work the .NET developers put into the stack!&lt;/p&gt;
&lt;p&gt;While browsing the source code I found some inaccuracies in the code XML comments, so I filed an issue to the ASP.NET Core team on GitHub &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues/39725&quot;&gt;here&lt;/a&gt; and it was quickly resolved within a few days. &lt;/p&gt;
&lt;p&gt;I have built training courses about this topic and it has helped several companies move forward with their journey. Check out the courses &lt;a href=&quot;https://tn-data.se/courses/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;more-posts-by-the-author&quot;&gt;More posts by the author&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;//net/missing-openid-connect-claims-in-asp-net-core/&quot;&gt;Debugging OpenID Connect claim problems in ASP.NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/asp-net-core-jwtbearer-library-whats-new/&quot;&gt;ASP.NET Core JwtBearer library: what’s new?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;//net/net-5-source-generators-mediatr-cqrs-omg/&quot;&gt;.NET 5 Source Generators – MediatR – CQRS – OMG!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related-training-workshops&quot;&gt;Related Training Workshops&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/building-asp-net-core-apis/&quot;&gt;Building ASP.NET Core APIs&lt;/a&gt; &lt;img alt=&quot;Building ASP.NET Core APIs - new training workshop&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;44&quot; height=&quot;16&quot; src=&quot;https://nestenius.se/_astro/new-badge-yellow-label.BuswNKOb_Z8ur7o.webp&quot; srcset=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tn-data.se/courses/asp-net-core-fundamentals/&quot;&gt;ASP.NET Core fundamentals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category></item><item><title>How I built my own Sega Mega Drive hardware dev kit from scratch</title><link>https://nestenius.se/hardware/how-i-built-my-own-sega-mega-drive-hardware-dev-kit-from-scratch/</link><guid isPermaLink="true">https://nestenius.se/hardware/how-i-built-my-own-sega-mega-drive-hardware-dev-kit-from-scratch/</guid><description>Over 30 years ago, I decided to take on the challenge of building my own Sega Mega Drive hardware dev kit from scratch. At the time, I was eager to</description><pubDate>Tue, 18 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;introduction&quot;&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Over &lt;strong&gt;30 years&lt;/strong&gt; ago, I decided to take on the challenge of building my own &lt;strong&gt;Sega Mega Drive hardware&lt;/strong&gt; dev kit from scratch. At the time, I was eager to develop games and wanted to target a powerful platform beyond the &lt;strong&gt;Atari 1040 STE&lt;/strong&gt; I had. After discovering the &lt;strong&gt;Sega Mega Drive&lt;/strong&gt; and getting hooked on its incredible hardware and games like Sonic and Revenge of Shinobi, I set out to create a dev kit without official documentation or a development kit, just a Sega Mega Drive and a lot of determination.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/Image2-1.jpg&quot;&gt;&lt;img alt=&quot;Photo of my sega mega drive with the cover&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1280&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-16-bit-console-top-view.p7zeU_ip_gCdkB.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Eventually, I got the crazy idea that I wanted to develop games for this machine. But surely I  would need some crazy expensive and hard-to-get hardware to do that? I had no official developer kit, no money to buy one, no documentation, just a Mega Drive.&lt;/p&gt;
&lt;p&gt;So I thought, how hard can it be to build my own kit? &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First some sanity checks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But wait? Was I crazy or what? Why did I think I would be able to pull this off?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Sega Mega Drive is based around the &lt;a href=&quot;https://en.wikipedia.org/wiki/Motorola_68000&quot;&gt;Motorola MC68000 CPU&lt;/a&gt;, a CPU that I was very familiar with on the lowest chip level. It also has a separate  &lt;a href=&quot;https://en.wikipedia.org/wiki/Zilog_Z80&quot;&gt;Z80&lt;/a&gt; co-processor for audio and other tasks that I was slightly familiar with.&lt;/li&gt;
&lt;li&gt;I was very familiar with the MC68000 &lt;a href=&quot;http://wpage.unina.it/rcanonic/didattica/ce1/docs/68000.pdf&quot;&gt;assembly language&lt;/a&gt;, after many years of low-level assembly demo-programming on the Atari ST home computer. I am one of the original members of SYNC, an Atari ST demo group in Sweden. You can read more about SYNC &lt;a href=&quot;http://www.lysator.liu.se/~celeborn/sync/page3.html&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://demozoo.org/groups/2256&quot;&gt;here&lt;/a&gt;, and we sure did some quite crazy things on the Atari ST back in the day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/Image3.jpg&quot;&gt;&lt;img alt=&quot;Sample wire-wrapped circuit boards that I did prior to my sega mega drive development kit project&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1440&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-homemade-dev-kit-mc68000-pcb-boards.BgrnQfdH_1gc7Tf.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had tinkered and played with electronics and built small computers for a long time – here are some of the random projects from back in the day. However, I don’t remember what they do :-) &lt;/p&gt;
&lt;h2 id=&quot;how-to-approach-the-challenge&quot;&gt;&lt;strong&gt;How to approach the challenge?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;My goal was to design and build a kit that, without modifying the Sega Mega Drive, would allow me to “simulate” a game cartridge using static ram memory. The kit would then be connected to my Atari ST computer where I would be doing the actual programming. An overview of the kit:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;High-level overview of my Sega Megadrive development circuit board&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;227&quot; height=&quot;298&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-devkit-cartridge-block-diagram.BlmHRoBG_Z1XAak.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-sega-mega-drive-cartridges&quot;&gt;&lt;strong&gt;The Sega Mega Drive Cartridges&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The first challenge was to reverse engineer a game cartridge and figure out the pin layout of the edge connector. The circuit board inside a cartridge looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/Image5.jpg&quot;&gt;&lt;img alt=&quot;Same Sega MegaDrive game cartridge circuit board with its edge connector and ROM chip.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1144&quot; height=&quot;515&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-cartridge-pcb-mpr-13252.D0rHAeM8_Z1szGDV.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After a lot of research in various chip reference books, I eventually figured out it was a standard ROM chip. With that knowledge it made it pretty simple to figure out what most pins on the edge connector were.&lt;/p&gt;
&lt;p&gt;However, there were still some other pins that I needed to figure out to be able to do a complete development kit.&lt;/p&gt;
&lt;h2 id=&quot;the-sega-mega-drive-game-slot&quot;&gt;&lt;strong&gt;The Sega Mega Drive Game Slot&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Figuring out what the pins meant on the cartridge connector inside the Mega Drive was the next challenge. Inside the console, the connector is located next to the 68K CPU and it looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/Image6.jpg&quot;&gt;&lt;img alt=&quot;The SegaMega drive game cartridge game connector&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1173&quot; height=&quot;577&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-motorola-mc68000-cpu-chip-pcb.DcD3k_1l_Z1n4l6M.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Using my multimeter, I was able to trace most of the connectors directly to the well-known pin layout of the CPU&lt;/p&gt;
&lt;p&gt;However, there were one or two pins where I could not figure out what they did. And without a proper logic analyzer, it would have been pretty hard to figure out what they did. As I was on a very tight budget, I decided to build my own. How hard could that be? After some thinking I figured out that all I needed was to get an 8-bit 2 KB FIFO buffer chip and use it to sample the signals when the console starts up after a reset sequence. By doing this, I could look at the signals and try to decode their relationship with some of the other well-known pins on the CPU bus. As I was very familiar with how the MC68K CPU bus works, I was up to the challenge.&lt;/p&gt;
&lt;p&gt;Basically, what I wanted to do was:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/Image-7.png&quot;&gt;&lt;img alt=&quot;Overview of the 2KB FIFO Buffer memory I used as a cheap 8-channel logic analyzer.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;938&quot; height=&quot;296&quot; src=&quot;https://nestenius.se/_astro/mega-drive-to-atari-st-8bit-2kb-fifo-buffer-diagram.W9TUPELj_1pS688.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;but-how-could-i-get-hold-of-a-fifo-buffer-chip&quot;&gt;But how could I get hold of a FIFO-buffer chip?&lt;/h2&gt;
&lt;p&gt;At the time I was staying in Paris with a friend, where we were finalizing a music sound tracker called Audio Sculpture for the Atari ST that we had sold to a French publisher.&lt;/p&gt;
&lt;p&gt;So, back to getting hold of the chip. I somehow found out about an electronics trade show in Paris, so I decided to go there with the goal of asking for a FIFO-buffer engineering sample. I eventually found a helpful company that would give me an engineering sample chip for free. Awesome!&lt;/p&gt;
&lt;p&gt;Here are some links about the Audio Sculpture sound tracker we published: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=76G-OH7lAGs&amp;#x26;ab_channel=Dbug%27sstuff&quot;&gt;Audio Sculpture Demo Disk&lt;/a&gt; by SYNC and Expose Software on Atari ST&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.atarimania.com/utility-atari-st-audio-sculpture_25173.html&quot;&gt;Audio Sculpture at Atari mania.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;but-how-would-i-get-the-data-out-of-the-fifo-buffer&quot;&gt;&lt;strong&gt;But how would I get the data out of the FIFO-buffer?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;My Atari ST had an expansion port that directly exposed the CPU bus inside the computer, so with some logic, I tied the FIFO-buffer to this port. I wrote some software to extract the data from the buffer and display it to the screen. Using this technique I was eventually able to figure out the purpose of the mysterious pins.&lt;/p&gt;
&lt;p&gt;Now I had solved the layout of the cartridge port in the console. On to the next challenge!&lt;/p&gt;
&lt;h2 id=&quot;getting-my-code-into-the-mega-drive&quot;&gt;&lt;strong&gt;Getting my code into the Mega Drive&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;With the hardware all figured out, I now needed to figure out how I would be able to write software for the console. &lt;/p&gt;
&lt;p&gt;My idea was to have a small boot firmware that, at startup, would wait for a binary data dump from my Atari ST. I would connect the kit to my Atari using a home-built transfer protocol that would send it over an 8-bit parallel cable, similar to the parallel printer post most PCs had back in the day.&lt;/p&gt;
&lt;p&gt;At startup the Mega Drive boots from the game cartridge, so all I had to do was to add a small boot-firmware that would, at startup, accept the binary data dump and, after receiving the dump, it should start to execute the code. I used two 8-bit EPROM chips to hold the actual firmware. &lt;/p&gt;
&lt;p&gt;I did manage to locate some of the source code for the firmware and I have published the code as a &lt;a href=&quot;https://gist.github.com/tndata/95b14a32c10a01124ce64f81234715e0&quot;&gt;GitHub GIST&lt;/a&gt; (Not sure if it is the final one) and in this &lt;a href=&quot;https://gist.github.com/tndata/313688ffe2e2d24b676e2264a9943d5c&quot;&gt;GIST.&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-development-kit-memory-board&quot;&gt;&lt;strong&gt;The development kit memory board&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;My design consisted of two circuit boards, one memory board that just held the static ram memory and one logic board for all the rest.&lt;/p&gt;
&lt;p&gt;To store the game I created a memory board that consisted of ten 128 KB static ram memory chips (&lt;a href=&quot;https://pdf1.alldatasheet.com/datasheet-pdf/view/37309/SAMSUNG/KM681000.html&quot;&gt;KM681000LP-8&lt;/a&gt;) with a total capacity of 1280 KB. At that time most games on the Mega Drive required 256–512 KB of storage while some needed more.&lt;/p&gt;
&lt;p&gt;Besides the memory chips, I added the following buffers to the memory board, just to be sure that I would not overload the system bus on the Mega Drive:  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two &lt;a href=&quot;https://assets.nexperia.com/documents/data-sheet/74HC_HCT245.pdf&quot;&gt;74HC245&lt;/a&gt; – 8-bit bi-directional transceiver for the data bus&lt;/li&gt;
&lt;li&gt;Three &lt;a href=&quot;https://www.alldatasheet.com/view.jsp?Searchword=HC541&amp;#x26;sField=3&quot;&gt;HC541&lt;/a&gt; – 8-bit driver for the address bus&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the memory board, I eventually designed some proper circuit boards and also created a few different revisions of the board – here are a few of them:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/memory-board.jpg&quot;&gt;&lt;img alt=&quot;Sample Sega Mega Drive development kit memory prototype boards&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1440&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-devkit-memory-boards-pcb-toshiba-eprom.BpMhQk22_Z1smFrc.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was able to extract the PCB layout files from the old DOS-based circuit board designer software that I used, using the &lt;a href=&quot;https://dosbox-x.com/&quot;&gt;DOSBox-X DOS emulator&lt;/a&gt;, and here is the result:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nestenius.se/wp-content/uploads/2021/12/RamCard.pdf&quot;&gt;&lt;img alt=&quot;My layout for the Sega Megadrive memory circuit board&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1518&quot; src=&quot;https://nestenius.se/_astro/16-bit-memory-card-rev2-pcb-layout-front-back.DAVQfPDW_Z14Kc2s.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-development-kit-logic-board&quot;&gt;&lt;strong&gt;The development kit logic board&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The second circuit board that this kit consisted of contained the following parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two EPROM chips that held the startup firmware&lt;/li&gt;
&lt;li&gt;Glue logic &lt;/li&gt;
&lt;li&gt;Connector to the memory board&lt;/li&gt;
&lt;li&gt;Connector to the host computer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The board went through several iterations, where the one in the middle is the latest and working prototype:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/MainBoard.jpg&quot;&gt;&lt;img alt=&quot;Sample prototypes of the main Sega mega drive development kit circuit board&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1440&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-dev-kit-hand-built-pcb-boards-three-modules.BfaLoRAl_271bvL.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For rapid prototyping I wire-wrapped these boards as you can see below:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/MainBoard2.jpg&quot;&gt;&lt;img alt=&quot;Examples of the wire wrapped backside of the prototype boards&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1920&quot; height=&quot;1440&quot; src=&quot;https://nestenius.se/_astro/sega-mega-drive-dev-kit-hand-wired-protoboards.BG4louXd_Z1sS4fX.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I started to design a real circuit board as you can see from the screenshots below, but unfortunately I never got around to actually building one using this board.&lt;/p&gt;
&lt;p&gt;[gallery link=“file” columns=“2” size=“full” ids=“1069,1070”]&lt;/p&gt;
&lt;h2 id=&quot;glue-logic&quot;&gt;&lt;strong&gt;Glue logic&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To tie up all the necessary logic, I added a small PAL (Programmable Array Logic) chip that contained some of the glue logic needed to tie it all up.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tn-data.se/wp-content/uploads/2021/12/Image-10.png&quot;&gt;&lt;img alt=&quot;Chip layout of the PAL (Programmable Array Logic) chip&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;611&quot; height=&quot;418&quot; src=&quot;https://nestenius.se/_astro/28-pin-dip-ic-pinout-diagram-clk-io-vcc.C_sWIDEF_XtBmM.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I managed to dig up some source code for this chip and I posted it as a GIST &lt;a href=&quot;https://gist.github.com/tndata/e79d6148df490fd45dba012bdb3bb5f4&quot;&gt;here&lt;/a&gt; for you to explore.&lt;/p&gt;
&lt;h2 id=&quot;schematics&quot;&gt;&lt;strong&gt;Schematics&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The schematics for the setup can be found &lt;a href=&quot;https://nestenius.se/wp-content/uploads/2021/12/Combined-schematics.pdf&quot;&gt;here&lt;/a&gt;. However, as version control was not a thing back then, I am not sure if this is the final schematics or not.&lt;/p&gt;
&lt;h2 id=&quot;did-it-work&quot;&gt;&lt;strong&gt;Did it work?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Yes, eventually I got it all to work and I was, for example, able to write simple applications and download them directly to the Mega Drive and run it. Eventually I got hold of enough detailed information to be able to control some sprites on the screen and control it using the joystick.&lt;/p&gt;
&lt;p&gt;To do the actual programming on my Atari ST, I used a tool called &lt;a href=&quot;https://github.com/sarnau/TurboAss&quot;&gt;Turbo Assembler&lt;/a&gt;.  Turbo Assembler was a very unique assembly language IDE as it would pre-generate the binary data as you typed. The second advantage with Turbo Assembler was that it allowed me to develop my own custom drivers/targets. This allowed me to send the final assembly directly down to my hardware kit! Awesome!&lt;/p&gt;
&lt;p&gt;Did I write any games? Unfortunately no. As I started my university studies, I didn’t have the time to pursue my game career any further. But it sure was a very fun project and I learned a lot on the way.&lt;/p&gt;
&lt;h2 id=&quot;the-final-thing&quot;&gt;&lt;strong&gt;The final thing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;So, what did the final project look like? Here are some photos:&lt;/p&gt;
&lt;p&gt;[gallery size=“full” link=“file” ids=“1073,1074,1075,1076,1077”]&lt;/p&gt;
&lt;h2 id=&quot;advanced-firmware&quot;&gt;&lt;strong&gt;Advanced firmware&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;As a side project I started to write a bit more advanced firmware with some more advanced debugging features and I’ve posted a copy of the source code &lt;a href=&quot;https://gist.github.com/tndata/313688ffe2e2d24b676e2264a9943d5c&quot;&gt;here&lt;/a&gt; for you to explore.&lt;/p&gt;
&lt;p&gt;It included features like debugging, setting breakpoints, downloading binary data, and other handy tools. I have no idea if it works or not.&lt;/p&gt;
&lt;h2 id=&quot;i-was-interviewed&quot;&gt;I was interviewed&lt;/h2&gt;
&lt;p&gt;As a follow-up to this blog post, I did an interview with &lt;a href=&quot;https://www.retrorgb.com&quot;&gt;RetroRGB&lt;/a&gt; that you can watch &lt;a href=&quot;https://www.retrorgb.com/interview-with-tore-nestenius.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.retrorgb.com/interview-with-tore-nestenius.html&quot;&gt;&lt;img alt=&quot;Retro RGB YouTube thumbnail showing an interview with Tore Nestinius and a custom Sega Mega Drive dev kit PCB&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;658&quot; height=&quot;369&quot; src=&quot;https://nestenius.se/_astro/retrorgb-youtube-interview-tore-nestinius-mega-drive-pcb.B1E32WE0_1j3h47.webp&quot; srcset=&quot;&quot;&gt;&lt;/a&gt;&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>hardware</category></item><item><title>TNValidate is now open source</title><link>https://nestenius.se/net/tnvalidate-is-now-open-source/</link><guid isPermaLink="true">https://nestenius.se/net/tnvalidate-is-now-open-source/</guid><description>Our internal validation library TNValidate is now available as a project on GitHub. We released this as open source because we wanted to let others take</description><pubDate>Tue, 01 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Our internal validation library TNValidate is now available as a project on &lt;a href=&quot;https://github.com/tndataab/TNValidate&quot;&gt;GitHub&lt;/a&gt;. We released this as open source because we wanted to let others take part in our internal libraries. The library is written in C# and uses a “Fluent Interface” to define the validation rules.&lt;/p&gt;
&lt;p&gt;By using TNValidate, we have achieved a uniform way to write our validation logic, which in turn has reduced the number of bugs and made our code more readable.&lt;/p&gt;
&lt;p&gt;Using TNValidate is very simple:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Instantiate validator. var Validate = new Validator(ValidationLanguageEnum.English);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Basic validation. Validate.That(Email, &quot;Email address&quot;).IsEmail();&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Chaining a couple of rules. Validate.That(Name, &quot;Name&quot;).IsLongerThan(3).IsShorterThan(100);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Supply a custom error message. Validate.That(Age, &quot;Age&quot;).IsGreaterThanOrEqual(13, &quot;You must be older than 13 to sign up&quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;// Check if validation succeeded. if (Validate.HasErrors()) { // Show errors (Validate.ValidatorResults can be data-bound). foreach (var Error in Validate.ValidatorResults) Console.WriteLine(Error.ValidationMessage); } else { // Was fine, continue... // ... }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Head over to &lt;a href=&quot;https://github.com/tndataab/TNValidate&quot;&gt;GitHub&lt;/a&gt; and give it a try!&lt;/p&gt;</content:encoded><dc:creator>Tore Nestenius</dc:creator><category>net</category><category>net</category></item></channel></rss>