<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Auth on Techformist</title>
    <link>https://techformist.com/tags/auth/</link>
    <description>Recent content in Auth on Techformist</description>
    <image>
      <url>https://techformist.com/logo.svg</url>
      <link>https://techformist.com/logo.svg</link>
    </image>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 01 Oct 2024 06:30:00 +0000</lastBuildDate><atom:link href="https://techformist.com/tags/auth/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Identity in .NET API Makes Auth Easy</title>
      <link>https://techformist.com/2024-10-01-identity-dotnet-api-auth-made-easy/</link>
      <pubDate>Tue, 01 Oct 2024 06:30:00 +0000</pubDate>
      
      <guid>https://techformist.com/2024-10-01-identity-dotnet-api-auth-made-easy/</guid>
      <description>&lt;p&gt;There was a time to be scared of the auth in ASP.NET. Identity really makes it easy.
But before delving any further, let&amp;rsquo;s keep the tradition alive by knowing the ancient Roman history of auth in ASP.NET.&lt;/p&gt;
&lt;h2 id=&#34;how-auth-was-done-earlier&#34;&gt;How Auth was done earlier?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Manual Token Generation: Developers manually created JWT tokens using libraries like System.IdentityModel.Tokens.Jwt and hardcoded key management.&lt;/li&gt;
&lt;li&gt;No Built-in User Management: Handling user registration, login, password hashing, and role management required custom code.&lt;/li&gt;
&lt;li&gt;Manual Claims Management: Claims (roles, permissions) were added to JWT tokens manually, increasing the risk of errors.&lt;/li&gt;
&lt;li&gt;Token Validation: Developers manually validated JWT tokens in each request, including signature, expiration, and claims validation.&lt;/li&gt;
&lt;li&gt;No Built-in Features for Role Management: Handling user roles and claims for authorization were complex&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s not uncommon to see code like this even today (there continue to be valid use cases, of course).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>There was a time to be scared of the auth in ASP.NET. Identity really makes it easy.
But before delving any further, let&rsquo;s keep the tradition alive by knowing the ancient Roman history of auth in ASP.NET.</p>
<h2 id="how-auth-was-done-earlier">How Auth was done earlier?</h2>
<ul>
<li>Manual Token Generation: Developers manually created JWT tokens using libraries like System.IdentityModel.Tokens.Jwt and hardcoded key management.</li>
<li>No Built-in User Management: Handling user registration, login, password hashing, and role management required custom code.</li>
<li>Manual Claims Management: Claims (roles, permissions) were added to JWT tokens manually, increasing the risk of errors.</li>
<li>Token Validation: Developers manually validated JWT tokens in each request, including signature, expiration, and claims validation.</li>
<li>No Built-in Features for Role Management: Handling user roles and claims for authorization were complex</li>
</ul>
<p>It&rsquo;s not uncommon to see code like this even today (there continue to be valid use cases, of course).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">key</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymmetricSecurityKey</span><span class="p">(</span><span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="n">GetBytes</span><span class="p">(</span><span class="s">&#34;YourSecretKey&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">creds</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SigningCredentials</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">SecurityAlgorithms</span><span class="p">.</span><span class="n">HmacSha256</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">claims</span> <span class="p">=</span> <span class="k">new</span><span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">Claim</span><span class="p">(</span><span class="n">JwtRegisteredClaimNames</span><span class="p">.</span><span class="n">Sub</span><span class="p">,</span> <span class="s">&#34;username&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">Claim</span><span class="p">(</span><span class="n">JwtRegisteredClaimNames</span><span class="p">.</span><span class="n">Jti</span><span class="p">,</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">ToString</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">token</span> <span class="p">=</span> <span class="k">new</span> <span class="n">JwtSecurityToken</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">issuer</span><span class="p">:</span> <span class="s">&#34;yourdomain.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">audience</span><span class="p">:</span> <span class="s">&#34;yourdomain.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">claims</span><span class="p">:</span> <span class="n">claims</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">expires</span><span class="p">:</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">.</span><span class="n">AddMinutes</span><span class="p">(</span><span class="m">30</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">signingCredentials</span><span class="p">:</span> <span class="n">creds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// create token</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">tokenString</span> <span class="p">=</span> <span class="k">new</span> <span class="n">JwtSecurityTokenHandler</span><span class="p">().</span><span class="n">WriteToken</span><span class="p">(</span><span class="n">token</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// time passes..</span>
</span></span><span class="line"><span class="cl"><span class="c1">//</span>
</span></span><span class="line"><span class="cl"><span class="c1">// time now to validate in another part of the code..</span>
</span></span><span class="line"><span class="cl"><span class="c1">// validate token</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">services</span><span class="p">.</span><span class="n">AddAuthentication</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span><span class="p">.</span><span class="n">DefaultAuthenticateScheme</span> <span class="p">=</span> <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span><span class="p">.</span><span class="n">DefaultChallengeScheme</span> <span class="p">=</span> <span class="n">JwtBearerDefaults</span><span class="p">.</span><span class="n">AuthenticationScheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">AddJwtBearer</span><span class="p">(</span><span class="n">options</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span><span class="p">.</span><span class="n">TokenValidationParameters</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TokenValidationParameters</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">ValidateIssuer</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ValidateAudience</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ValidateLifetime</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ValidateIssuerSigningKey</span> <span class="p">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ValidIssuer</span> <span class="p">=</span> <span class="s">&#34;yourdomain.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">ValidAudience</span> <span class="p">=</span> <span class="s">&#34;yourdomain.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">IssuerSigningKey</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymmetricSecurityKey</span><span class="p">(</span><span class="n">Encoding</span><span class="p">.</span><span class="n">UTF8</span><span class="p">.</span><span class="n">GetBytes</span><span class="p">(</span><span class="s">&#34;YourSecretKey&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span></code></pre></div><h2 id="roll-in-identity">Roll-in Identity</h2>
<ul>
<li>Built-in User and Role Management
<ul>
<li>Identity provides a complete user management system with built-in support for user registration, login, password hashing, and role management.</li>
<li>No need to manually store user credentials or roles in the database; Identity handles it through its own entity models (e.g., IdentityUser, IdentityRole).</li>
</ul>
</li>
<li>Automatic Token Generation and Validation
<ul>
<li>Identity integrates easily with JWT authentication, automatically handling token generation, validation, and expiration management.</li>
<li>Developers no longer need to manually create or validate tokens using JwtSecurityTokenHandler.</li>
</ul>
</li>
<li>Claims and Role Management: Manage claims and roles through simple, high-level APIs (UserManager, RoleManager), which abstract away the complexity of managing claims in the token payload manually.</li>
<li>Out-of-the-Box (&ldquo;OOB&rdquo;) Security
<ul>
<li>OOB industry-standard security practices such as password hashing (with customizable password policies) and token expiration management - reduce security risks.</li>
<li>Built-in support for multi-factor authentication (MFA), account lockout, and password recovery without custom implementations.</li>
</ul>
</li>
<li>Simplified Middleware Integration
<ul>
<li>Easily integrated into the ASP.NET middleware pipeline - no need to manually configure authentication schemes or token validation parameters.</li>
<li>Built-in Identity middleware automatically manages authentication cookies and tokens.</li>
</ul>
</li>
<li>Flexible Data Store: Work with multiple data stores (SQL Server, MySQL, etc.) by default</li>
<li>Support for External Authentication Providers: Integrating OAuth providers like Google, Facebook, and Microsoft is simplified</li>
</ul>
<h2 id="identity-in-action-a-simple-demo">Identity in Action: A Simple Demo</h2>
<p>Create a new project..</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet new webapi -n dotnet-api-auth-demo
</span></span></code></pre></div><p>Add the below packages..</p>
<ul>
<li>Microsoft.EntityFrameworkCore.Sqlite</li>
<li>Microsoft.EntityFrameworkCore.Design</li>
<li>Microsoft.EntityFrameworkCore.Tools</li>
<li>Microsoft.AspNetCore.Identity.EntityFrameworkCore</li>
</ul>
<p>The first three are for Entity Framework Core and for using a SQLite database. The last one is for Identity.</p>
<p>Create the <code>ApplicationDbContext</code> class like as God intended. However, inherit from <code>IdentityDbContext</code> this time.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Identity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.AspNetCore.Identity.EntityFrameworkCore</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">Microsoft.EntityFrameworkCore</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">ApplicationDbContext</span> <span class="p">:</span> <span class="n">IdentityDbContext</span><span class="p">&lt;</span><span class="n">IdentityUser</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">public</span> <span class="n">ApplicationDbContext</span><span class="p">(</span><span class="n">DbContextOptions</span><span class="p">&lt;</span><span class="n">ApplicationDbContext</span><span class="p">&gt;</span> <span class="n">options</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">options</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>IdentityDbContext</code> will add magic that we are about to experience.</p>
<p>Add three lines of code in <code>Program.cs</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddAuthorization</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">builder</span><span class="p">.</span><span class="n">Services</span><span class="p">.</span><span class="n">AddIdentityApiEndpoints</span><span class="p">&lt;</span><span class="n">IdentityUser</span><span class="p">&gt;().</span><span class="n">AddEntityFrameworkStores</span><span class="p">&lt;</span><span class="n">ApplicationDbContext</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="c1">// add the above lines before initializing `app`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">app</span> <span class="p">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">Build</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// add the below line after `app` is initialized</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">MapIdentityApi</span><span class="p">&lt;</span><span class="n">IdentityUser</span><span class="p">&gt;();</span>
</span></span></code></pre></div><ul>
<li><code>AddEntityFrameworkStores</code> shows which database to use. In this case, we are using SQLite and using the main <code>ApplicationDbContext</code> itself. We could have used a different auth database as easily.</li>
<li><code>MapIdentityApi</code> automatically adds the necessary routes for Identity.</li>
</ul>
<p>Since we have no other endpoints, add auth to the default <code>weatherforecast</code> endpoint using <code>RequireAuthorization()</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cs" data-lang="cs"><span class="line"><span class="cl"><span class="n">app</span><span class="p">.</span><span class="n">MapGet</span><span class="p">(</span><span class="s">&#34;/weatherforecast&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">forecast</span> <span class="p">=</span> <span class="n">Enumerable</span><span class="p">.</span><span class="n">Range</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">5</span><span class="p">).</span><span class="n">Select</span><span class="p">(</span><span class="n">index</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">WeatherForecast</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">DateOnly</span><span class="p">.</span><span class="n">FromDateTime</span><span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">.</span><span class="n">AddDays</span><span class="p">(</span><span class="n">index</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">            <span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="n">Next</span><span class="p">(-</span><span class="m">20</span><span class="p">,</span> <span class="m">55</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">summaries</span><span class="p">[</span><span class="n">Random</span><span class="p">.</span><span class="n">Shared</span><span class="p">.</span><span class="n">Next</span><span class="p">(</span><span class="n">summaries</span><span class="p">.</span><span class="n">Length</span><span class="p">)]</span>
</span></span><span class="line"><span class="cl">        <span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">ToArray</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">forecast</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">WithName</span><span class="p">(</span><span class="s">&#34;GetWeatherForecast&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">WithOpenApi</span><span class="p">().</span><span class="n">RequireAuthorization</span><span class="p">();</span>
</span></span></code></pre></div><p>Do a build to ensure there are no errors.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet build
</span></span></code></pre></div><p>Migrate the database.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet ef migrations add InitAuth -o Data/Migrations
</span></span><span class="line"><span class="cl">dotnet ef database update
</span></span></code></pre></div><p>Run the application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet run
</span></span></code></pre></div><p>Navigate to <code>http://localhost:5001/swagger/index.html</code> to see the Identity endpoints.</p>
<p><img loading="lazy" src="/2024/swagger-dotnet-identity-endpoints.png" type="" alt="swagger-dotnet-identity-endpoints"  /></p>
<p>You can test the auth endpoints using Swagger, or as I prefer - Insomnia/Postman REST clients.</p>
<p>First, do a GET to <code>http://localhost:5001/weatherforecast</code> to see a 401 Unauthorized response.</p>
<p><img loading="lazy" src="/2024/unauthorized-identity-endpoint.png" type="" alt="unauthorized-identity-endpoints"  /></p>
<p>Now, call the <code>register</code> to create a user, and subsequently, the <code>login</code> endpoint to get a token.</p>
<p>POST http://localhost:5001/register</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;a1@a.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;Pass@123&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>POST http://localhost:5001/login</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;a1@a.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;Pass@123&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You should get the token response.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;tokenType&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;accessToken&#34;</span><span class="p">:</span> <span class="s2">&#34;CfDJ8AcfSlDrZbhNlphsDvuO8gDgQty5M...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;expiresIn&#34;</span><span class="p">:</span> <span class="mi">3600</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;refreshToken&#34;</span><span class="p">:</span> <span class="s2">&#34;CfDJ8AcfSlDrZbhNlphsDvuO8gD60O...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Use the access token to call the <code>weatherforecast</code> endpoint and see the glorious response.</p>
<p><img loading="lazy" src="/2024/identity-asp-dotnet-token-api-call.png" type="" alt="identity-asp-dotnet-token-api-call"  /></p>
<p>We have just scratched the surface of Identity in .NET. There is a lot more to explore, including:</p>
<ul>
<li>Account lockout/ recovery</li>
<li>Password policies</li>
<li>Claims and roles management</li>
<li>Customizing Identity</li>
<li>Using external providers</li>
<li>Role-based authorization</li>
<li>Multi-factor authentication</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Check out the full code on <a href="https://github.com/techformist/dotnet-api-auth-demo/">GitHub</a>.</p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
