<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Robin van der Knaap</title>
    <description>The latest articles on DEV Community by Robin van der Knaap (@robinvanderknaap).</description>
    <link>https://dev.to/robinvanderknaap</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F490348%2F2619e988-eec1-4df2-949a-eaa885442eaf.png</url>
      <title>DEV Community: Robin van der Knaap</title>
      <link>https://dev.to/robinvanderknaap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robinvanderknaap"/>
    <language>en</language>
    <item>
      <title>How to deploy a static website to a $4 Droplet at DigitalOcean</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Wed, 23 Oct 2024 15:19:19 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/how-to-deploy-a-static-website-to-digitalocean-308l</link>
      <guid>https://dev.to/robinvanderknaap/how-to-deploy-a-static-website-to-digitalocean-308l</guid>
      <description>&lt;p&gt;This guide describes how to deploy a static website to a $4 Droplet at &lt;a href="https://www.digitalocean.com/" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;. We will be using &lt;a href="https://nginx.org/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt; to serve our website and &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; to manage TLS certificates issued by &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's Encrypt&lt;/a&gt;. Finally, we setup GitHub Actions to automate the deployment of the website.&lt;/p&gt;

&lt;p&gt;Easier alternatives to deploy and host static websites are available of course, most notably &lt;a href="https://www.cloudflare.com/developer-platform/pages/" rel="noopener noreferrer"&gt;Cloudflare Pages&lt;/a&gt;, &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; and &lt;a href="https://render.com/" rel="noopener noreferrer"&gt;Render&lt;/a&gt;. But sometimes you want to have close control over your webserver, or you don't want these parties to manage your DNS, which is usually required. In that case, managing your own server at is a great solution. I like DigitalOcean for hosting my virtual machines (called Droplets), but with a little imagination you can apply this guide to any other provider of virtual machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DigitalOcean account.&lt;/li&gt;
&lt;li&gt;GitHub account, if you want to automatically deploy your website using GitHub Actions.&lt;/li&gt;
&lt;li&gt;SSH client, which is usually already available in your OS.&lt;/li&gt;
&lt;li&gt;Domain name and access to DNS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup Droplet
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SSH key pair
&lt;/h3&gt;

&lt;p&gt;Droplets can be accessed using SSH. Before we create a Droplet in the DigitalOcean Control Panel, we need to make sure we have a valid SSH key pair installed on our local machine and upload the public key to DigitalOcean.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is also possible to access the Droplet using username and password, but this is not recommended, access via SSH is much more secure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use &lt;a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent" rel="noopener noreferrer"&gt;this article&lt;/a&gt; to create a SSH key-pair if you don't already have one and make sure it is added to your SSH agent (which is described in the same article). For this guide I use a SSH  key-pair without a passphrase.&lt;/p&gt;

&lt;p&gt;Login to the DigitalOcean Control Panel, go to Settings -&amp;gt; Security:&lt;/p&gt;

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

&lt;p&gt;Use the &lt;code&gt;Add SSH Key&lt;/code&gt; button:&lt;/p&gt;

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

&lt;p&gt;You need to add the public key of your SSH key-pair. You can get the content of the public key like this in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat ~/.ssh/name-of-your-key.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy-paste the output inside the &lt;code&gt;Public Key&lt;/code&gt; field and give the key a proper name, so you can identify it later on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Droplet
&lt;/h3&gt;

&lt;p&gt;Now we are ready to create a new Droplet using the 'Create' button in the header of the Control Panel. Select the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Region and datacenter: Select the region and datacenter that's nearest to your customers&lt;/li&gt;
&lt;li&gt;Use the default VPC&lt;/li&gt;
&lt;li&gt;Image: Ubuntu latest version&lt;/li&gt;
&lt;li&gt;Droplet type: Basic&lt;/li&gt;
&lt;li&gt;CPU options:

&lt;ul&gt;
&lt;li&gt;Regular, Disktype SSD&lt;/li&gt;
&lt;li&gt;$4 per month instance (scroll to the left to make it visible)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Authentication method: SSH Key

&lt;ul&gt;
&lt;li&gt;Pick the SSH key you have just uploaded&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Add improved metrics monitoring and alerting (it's free)&lt;/li&gt;

&lt;li&gt;Advanced options (expand to reveal the IPv6 option)

&lt;ul&gt;
&lt;li&gt;Enable IPv6 (doing this later requires manual configuration)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Quantity: 1 Droplet&lt;/li&gt;

&lt;li&gt;Specify a proper host name so you can easily identify the Droplet in your Control Panel&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Use the 'Create Droplet' button to start the provisioning of the Droplet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reserved IP
&lt;/h3&gt;

&lt;p&gt;When the Droplet is created, it is assigned an IPv4 address automatically. In case our Droplet becomes unstable and we want to create a new one, we want to have a fixed IPv4 address so we don't have to update our DNS.&lt;/p&gt;

&lt;p&gt;DigitalOcean offers this service in the form of Reserved IP addresses. This is a free service as long as your reserved IP is assigned to a Droplet. Due to shortages in IPv$ addresses, DigitalOcean wants to prevent holding on to unused IPv4 addresses, and makes you pay for a reserved IP when you don't use it.&lt;/p&gt;

&lt;p&gt;Visit your Droplet in the Control Panel and select &lt;code&gt;enable now&lt;/code&gt; next to the &lt;code&gt;Reserved IP&lt;/code&gt; label.&lt;/p&gt;

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

&lt;p&gt;Follow the instructions. Once the &lt;code&gt;reserved ip&lt;/code&gt; is created, we can use it to update the DNS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reserved IPv6 addresses are not supported by DigitalOcean, in case of a change we need to manually update the DNS for IPv6 addresses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Update DNS
&lt;/h3&gt;

&lt;p&gt;Visit your DNS provider and add the following records to the DNS (you can skip the records for the &lt;code&gt;www&lt;/code&gt; sub-domain if you want, especially if you are deploying to a sub-domain instead of a root domain):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A record for &lt;code&gt;your-domain&lt;/code&gt; pointing to the reserved &lt;strong&gt;IPv4&lt;/strong&gt; address&lt;/li&gt;
&lt;li&gt;A record for &lt;code&gt;www.your-domain&lt;/code&gt; pointing to the reserved &lt;strong&gt;IPv4&lt;/strong&gt; address&lt;/li&gt;
&lt;li&gt;AAAA record for &lt;code&gt;your-domain&lt;/code&gt; pointing to the &lt;strong&gt;IPv6&lt;/strong&gt; address&lt;/li&gt;
&lt;li&gt;AAAA record for &lt;code&gt;www.your-domain&lt;/code&gt; pointing to the &lt;strong&gt;IPv6&lt;/strong&gt; address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;A&lt;/code&gt; records are for IPv4 traffic, this will be the bulk of your visitors and &lt;code&gt;AAAA&lt;/code&gt; records are for IPv6 traffic.&lt;/p&gt;

&lt;p&gt;You could also add &lt;a href="https://letsencrypt.org/docs/caa/" rel="noopener noreferrer"&gt;CAA&lt;/a&gt; records for making sure only Let's Encrypt is allowed to issue certificates for this domain. I'll skip this for now, because adding the correct values of CAA records differ per DNS provider. I don't want you to get stuck later on in this guide when we setup a certificate, due to incorrect CAA DNS records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Droplet using SSH
&lt;/h3&gt;

&lt;p&gt;To access your droplet, you can now use SSH to connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@your-reserved-ip-address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or when the DNS changes are propagated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@your-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Firewall
&lt;/h3&gt;

&lt;p&gt;After accessing the Droplet, the first thing we need to do is enable the firewall. We could use DigitalOcean's firewall, but we'll be using the &lt;a href="https://en.wikipedia.org/wiki/Uncomplicated_Firewall" rel="noopener noreferrer"&gt;UFW firewall&lt;/a&gt; that is installed with Ubuntu. It's not recommended to use both.&lt;/p&gt;

&lt;p&gt;Before we enable the firewall, we need to allow OpenSSH access, otherwise we lock ourselves out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw allow OpenSSH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can enable the firewall:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Unattended upgrades
&lt;/h3&gt;

&lt;p&gt;Make sure &lt;code&gt;Unattended upgrades&lt;/code&gt; is enabled in order to automatically retrieve and install security patches and other essential upgrades for your server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status unattended-upgrades.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to allow reboots after updates, you need to edit the configuration file of the Unattended Updates Service to enable reboots when required. This is usually the case when the kernel is updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/apt/apt.conf.d/50unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the line &lt;code&gt;Unattended-Upgrade::Automatic-Reboot&lt;/code&gt;, uncomment and set to &lt;code&gt;true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Automatically reboot *WITHOUT CONFIRMATION* if
//  the file /var/run/reboot-required is found after the upgrade
Unattended-Upgrade::Automatic-Reboot "true";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file by pressing &lt;code&gt;Ctrl+X&lt;/code&gt; to exit, then when prompted to save, &lt;code&gt;Y&lt;/code&gt; and hit &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Restart the Unattended Updates service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart unattended-upgrades.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More information on the Unattended Upgrades Service: &lt;a href="https://linux-audit.com/using-unattended-upgrades-on-debian-and-ubuntu/" rel="noopener noreferrer"&gt;https://linux-audit.com/using-unattended-upgrades-on-debian-and-ubuntu/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and configure Nginx
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;

&lt;p&gt;Make sure you are connected to your Droplet via SSH and use the following commands to install Nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update
apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update firewall
&lt;/h3&gt;

&lt;p&gt;We need to allow Nginx to pass through the firewall. Applications can register their profiles with UFW upon installation. These profiles allow UFW to manage these applications by name. Nginx also registers its profile upon installation. You can list the available applications like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw app list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will allow Nginx Full, which contains both HTTP and HTTPS connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw allow &lt;span class="s1"&gt;'NGINX Full'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the following command to view the current status of the firewall&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status should show that OpenSSH and Nginx FULL are allowed on the firewall for both IPv4 and IPv6 addresses.&lt;/p&gt;

&lt;p&gt;If you browse to &lt;a href="http://your-domain" rel="noopener noreferrer"&gt;http://your-domain&lt;/a&gt; you should see the Nginx welcome message.&lt;/p&gt;

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

&lt;p&gt;Sometimes the browser forces you to use HTTPS, and you cannot view the website yet. Don't worry, when the certificate is installed, it will work. In the mean time you can use curl to view the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl your-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;With the Nginx web server, &lt;em&gt;server blocks&lt;/em&gt; can be used to host more than one domain from a single server.&lt;/p&gt;

&lt;p&gt;We leave the default server block in place to be served if a client request does not match any other site. We will set up a new server block for our domain.&lt;/p&gt;

&lt;p&gt;Make you sure you are logged into your server and create a folder for your domain which will contain the the website's content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/www/your-domain/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a sample index.html file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /var/www/your-domain/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add some content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Yes&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;It's working&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file by pressing &lt;code&gt;Ctrl+X&lt;/code&gt; to exit, then when prompted to save, &lt;code&gt;Y&lt;/code&gt; and hit &lt;code&gt;Enter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In order for Nginx to serve this content, we also need to create a configuration file containing the server block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/nginx/sites-available/your-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuration is almost the same as the default server block, except for the root directory and the server name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/your-domain/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;your-domain&lt;/span&gt; &lt;span class="s"&gt;www.your-domain&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to update the configuration with the correct domain name. I've included a variant with the &lt;code&gt;www&lt;/code&gt; subdomain, if your are not using the &lt;code&gt;www&lt;/code&gt; sub-domain, you can leave it out of the configuration file.&lt;/p&gt;

&lt;p&gt;Enable the server block by creating a symlink of the configuration file to the &lt;code&gt;sites-enabled&lt;/code&gt; directory, which Nginx reads from during startup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/your-domain /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Nginx uses a common practice called symbolic links, or symlinks, to track which of your server blocks are enabled. Creating a symlink is like creating a shortcut on disk, so that you could later delete the shortcut from the &lt;code&gt;sites-enabled&lt;/code&gt; directory while keeping the server block in &lt;code&gt;sites-available&lt;/code&gt; if you wanted to enable it.&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now restart Nginx to apply the new configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check &lt;a href="http://your-domain" rel="noopener noreferrer"&gt;http://your-domain&lt;/a&gt; with your browser or curl, you should see the 'it's working' message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certbot
&lt;/h2&gt;

&lt;p&gt;We haven't installed a TLS certificate yet. We'll be using Certbot to provision a certificate from Let's Encrypt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Certbot
&lt;/h3&gt;

&lt;p&gt;We  install Certbot using &lt;code&gt;snap&lt;/code&gt;, this is the recommended way. Snap automatically updates Certbot. Source: &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;https://certbot.eff.org/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Login to your server and install Certbot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snap &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--classic&lt;/span&gt; certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute the following instruction in the terminal of the server to ensure that the &lt;code&gt;certbot&lt;/code&gt; command can be run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /snap/bin/certbot /usr/bin/certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provision certificate
&lt;/h3&gt;

&lt;p&gt;Certbot needs to be able to find the correct &lt;code&gt;server&lt;/code&gt; block in our Nginx configuration for it to be able to automatically configure TLS. Specifically, it does this by looking for a &lt;code&gt;server_name&lt;/code&gt; directive that matches the domain you request a certificate for. The &lt;code&gt;server_name&lt;/code&gt; is defined in our server configuration file, it's called &lt;code&gt;your-domain&lt;/code&gt; and &lt;code&gt;www.your-domain&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use Certbot to provision the certificate (skip the &lt;code&gt;www&lt;/code&gt; domain if you are not using it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; your-domain &lt;span class="nt"&gt;-d&lt;/span&gt; www.your-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the email address at which you want to receive urgent renewal and security notices from Letsencrypt. Accept the terms and the provisioning of the certificate starts.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://your-domain" rel="noopener noreferrer"&gt;https://your-domain&lt;/a&gt; in the browser, you should be able to connect via HTTPS now.&lt;/p&gt;

&lt;p&gt;You can test the certificate at &lt;a href="https://www.ssllabs.com/ssltest/" rel="noopener noreferrer"&gt;https://www.ssllabs.com/ssltest/&lt;/a&gt;. You should receive an A grade, which is nice for a change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto renew cert
&lt;/h3&gt;

&lt;p&gt;Let’s Encrypt’s certificates are only valid for ninety days. This is to encourage users to automate their certificate renewal process. The Certbot packages on your system come with a cron job or systemd timer that will renew your certificates automatically before they expire. You will not need to run Certbot again, unless you change your configuration. You can test automatic renewal for your certificates by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security headers
&lt;/h2&gt;

&lt;p&gt;To tighten security we can add some response headers to our server configuration file. We will add the following headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security" rel="noopener noreferrer"&gt;Strict-Transport-Security&lt;/a&gt;: Forces a web browser to connect directly via HTTPS when revisiting your website. This helps preventing man-in-the-middle attacks. Also known as HSTS.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options" rel="noopener noreferrer"&gt;X-Content-Type-Options&lt;/a&gt;: Avoids &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types#mime_sniffing" rel="noopener noreferrer"&gt;mime sniffing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy" rel="noopener noreferrer"&gt;Referrer-Policy&lt;/a&gt;: Controls how much referrer information (sent with the Referer header) should be included with requests.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options" rel="noopener noreferrer"&gt;X-Frame-Options&lt;/a&gt;: Used to indicate whether a browser should be allowed to render a page in a frame.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="noopener noreferrer"&gt;Content-Security-Policy&lt;/a&gt;: Helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edit the server configuration file for you domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/nginx/sites-available/your-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You may notice the config has been altered by Certbot to allow for HTTPS traffic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Add the following headers in the server section where the root is specified, you can add it after the &lt;code&gt;location&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Strict-Transport-Security&lt;/span&gt; &lt;span class="s"&gt;"max-age=31536000"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Referrer-Policy&lt;/span&gt; &lt;span class="s"&gt;"same-origin"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;"DENY"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Security-Policy&lt;/span&gt; &lt;span class="s"&gt;"default-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;base-uri&lt;/span&gt; &lt;span class="s"&gt;'none'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;frame-ancestors&lt;/span&gt; &lt;span class="s"&gt;'none'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;form-action&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Nginx for the changes to take effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a tool like &lt;a href="https://internet.nl" rel="noopener noreferrer"&gt;internet.nl&lt;/a&gt; to test the configuration of your web server for security issues.&lt;/p&gt;

&lt;p&gt;With a real website, you will probably have to alter the Content-Security-Policy (CSP) to allow for loading assets from certain external sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content
&lt;/h2&gt;

&lt;p&gt;Now that the web server is up-and-running and correctly configured, you can copy the content of your static site from your local machine. We will automate this later with GitHub Actions, but for now we do it manually.&lt;/p&gt;

&lt;p&gt;Let's create an example website on your local machine. Start with creating a folder which will contain the content. Make sure you are working on you local machine, exit the ssh session if you are still working on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-awesome-website
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the &lt;code&gt;index.html&lt;/code&gt; as welcome message&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; ./my-awesome-website/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following content to your index.html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-US"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My awesome website&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;This is where the magic happens&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our website is ready, we can copy the content from the folder to the server like this (run this command from your local machine, not when logged into your server):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-a&lt;/span&gt; ./my-awesome-website/ root@your-domain:/var/www/your-domain/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you visit your domain in the browser, you should see your site now.&lt;/p&gt;

&lt;p&gt;Instead of using the example website, you can point rsync to the folder with your own content to send it to the server. Make sure you have all the content you want to deploy in one folder. Most static site generators have an output folder which you can use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom error pages
&lt;/h2&gt;

&lt;p&gt;Nginx includes some default error pages, usually you want to serve your own error pages.&lt;/p&gt;

&lt;p&gt;First create a custom error page on your local machine&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; ./my-awesome-website/404.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add some content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;404 - You are definitely in the wrong place&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your website with the same rsync command we used before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-a&lt;/span&gt; ./my-awesome-website/ root@your-domain:/var/www/your-domain/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Nginx to use your custom error page, you need to change  the server configuration of your domain to point to the 404 page in your content. Make sure you logged into the server and edit the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/nginx/sites-available/your-domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following to the server configuration in the same block as the security headers (don't forget to use your own domain):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;error_page&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="n"&gt;/404.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/404.html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/your-domain/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Nginx&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 404 page should be showing now. You can repeat this process for other status codes, like &lt;code&gt;500 - Internal Server Error&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Instead of copying the content manually every time we want to update the site, it's better to automate the deployment. We can use GitHub Actions for this, if your code is hosted at GitHub.&lt;/p&gt;

&lt;p&gt;GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate all kinds of software tasks, like building and deploying your website. You can create workflows that are triggered whenever you push a change to your repository.&lt;/p&gt;

&lt;p&gt;First, we will to add our code to GitHub. If you already host your code at GitHub you can skip this part. We will add our private SSH key to the repository secrets to enable GitHub Actions to access your server. Finally we will create a workflow that deploys our website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create repository
&lt;/h3&gt;

&lt;p&gt;Create a repository at &lt;a href="https://github.com" rel="noopener noreferrer"&gt;https://github.com&lt;/a&gt;. Create a public or private repository. Decide if you want to create a &lt;code&gt;.gitignore&lt;/code&gt;, licence or &lt;code&gt;README.md&lt;/code&gt; file, it doesn't matter for the example website.&lt;/p&gt;

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

&lt;p&gt;The default branch of the GitHub repository is called &lt;code&gt;main&lt;/code&gt;. The name of the default branch in the local repository should be the same. Git uses &lt;code&gt;master&lt;/code&gt; by default, but you can override this default. Initialize the local GIT repository with the following command to use &lt;code&gt;main&lt;/code&gt; as the default branch name from within your website's folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-awesome-website
git init &lt;span class="nt"&gt;--initial-branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can add the remote repository to your local repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin git@github.com:robinvanderknaap/my-awesome-website.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to use the correct git URL of your repository, you can't use mine :)&lt;/p&gt;

&lt;p&gt;Pull the files from the remote repository if you added&lt;code&gt;README.md&lt;/code&gt;, license or &lt;code&gt;.gitignore&lt;/code&gt; file during the creation of your GitHub repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git pull origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now commit the changes to the local repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"My awesome website"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And push the changes to the remote repository and set the upstream branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;--set-upstream&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add private SSH key as repository secret
&lt;/h3&gt;

&lt;p&gt;For GitHub to deploy to your site, it needs to have access to the private key of the SSH key-pair we created at the beginning of this guide. We will store the key in a repository secret called &lt;code&gt;SSH_KEY_DEPLOY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Browse to your repository at GitHub and navigate to the repository settings. Select &lt;code&gt;Secrets and variables -&amp;gt; Actions&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Use the &lt;code&gt;New repository secret&lt;/code&gt; button to create a new secret called &lt;code&gt;SSH_KEY_DEPLOY&lt;/code&gt;. Paste the contents of your private key into the &lt;code&gt;secret&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;You can get the content of your private key like this (don't accidentally use the public key with the &lt;code&gt;.pub&lt;/code&gt; extension):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/name-of-your-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create workflow file for deployment
&lt;/h3&gt;

&lt;p&gt;GitHub Actions are triggered by adding workflow files to your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ./.github/workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workflows are declared using YAML files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch&lt;/span&gt; ./.github/workflows/deploy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy website&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get latest code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment if you need Node.js to build your site&lt;/span&gt;
&lt;span class="c1"&gt;#   - name: Use Node.js&lt;/span&gt;
&lt;span class="c1"&gt;#      uses: actions/setup-node@v2&lt;/span&gt;
&lt;span class="c1"&gt;#      with:&lt;/span&gt;
&lt;span class="c1"&gt;#        node-version: '20'&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Project&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;# Add steps here to build you website&lt;/span&gt;
        &lt;span class="s"&gt;# npm install&lt;/span&gt;
        &lt;span class="s"&gt;# npm run build&lt;/span&gt;
        &lt;span class="s"&gt;mkdir ./public&lt;/span&gt;
        &lt;span class="s"&gt;mv index.html ./public&lt;/span&gt;
        &lt;span class="s"&gt;mv 404.html ./public&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rsync&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;burnett01/rsync-deployments@7.0.1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;switches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-avzr --delete&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public/&lt;/span&gt;
        &lt;span class="na"&gt;remote_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/your-domain/html&lt;/span&gt;
        &lt;span class="na"&gt;remote_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your-domain&lt;/span&gt;
        &lt;span class="na"&gt;remote_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;remote_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_KEY_DEPLOY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty straightforward workflow. The script will trigger only when commits are pushed to the &lt;code&gt;main&lt;/code&gt; branch and contains the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pull code from repository&lt;/li&gt;
&lt;li&gt;Build website. You can add commands here to build your site. If you need Node.js to build your site, uncomment the &lt;code&gt;Use Node.js&lt;/code&gt; step. For the example website, we just copy our two html files to a public folder.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/Burnett01/rsync-deployments" rel="noopener noreferrer"&gt;plugin&lt;/a&gt; is used to rsync the contents to our server

&lt;ul&gt;
&lt;li&gt;Make sure to use the correct &lt;code&gt;path&lt;/code&gt; from which to deploy content&lt;/li&gt;
&lt;li&gt;Don't forget to specify the correct domain at &lt;code&gt;remote-path&lt;/code&gt; and &lt;code&gt;remote-host&lt;/code&gt; properties.&lt;/li&gt;
&lt;li&gt;Notice the SSH Key is retrieved from the repository secrets.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The workflow script is triggered every time you push a commit to the &lt;code&gt;main&lt;/code&gt; branch of the remote repository. and the website will automatically be deployed.&lt;/p&gt;

&lt;p&gt;Commit and push the build script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Added deploy workflow"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view the progress of your deployment in the &lt;code&gt;Actions&lt;/code&gt; tab of your repository at GitHub.&lt;/p&gt;

&lt;p&gt;That's it! Happy coding!&lt;/p&gt;

&lt;h2&gt;
  
  
  Used articles
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-20-04" rel="noopener noreferrer"&gt;Initial Server Setup with Ubuntu 20.04 | DigitalOcean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-22-04" rel="noopener noreferrer"&gt;How To Install Nginx on Ubuntu 22.04 | DigitalOcean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04" rel="noopener noreferrer"&gt;How To Secure Nginx with Let's Encrypt on Ubuntu 20.04 | DigitalOcean&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://linux-audit.com/using-unattended-upgrades-on-debian-and-ubuntu/" rel="noopener noreferrer"&gt;https://linux-audit.com/using-unattended-upgrades-on-debian-and-ubuntu/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssg</category>
      <category>githubactions</category>
      <category>nginx</category>
      <category>certbot</category>
    </item>
    <item>
      <title>Setup HUGO using Docker</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Sat, 11 Mar 2023 17:48:50 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setup-hugo-using-docker-43pm</link>
      <guid>https://dev.to/robinvanderknaap/setup-hugo-using-docker-43pm</guid>
      <description>&lt;p&gt;In this article I'm going to describe how to setup HUGO without installing a local version of HUGO on your machine by using  a Docker image instead.&lt;/p&gt;

&lt;p&gt;When choosing a specific Docker image, be aware that HUGO comes in two editions, standard and extended. The extended edition supports Sass and encoding WebP images, it is recommended by the HUGO team to use the extended edition, and that's what we will do in this article.&lt;/p&gt;

&lt;p&gt;We will be using a Docker image from this repository: &lt;a href="https://hub.docker.com/r/klakegg/hugo" rel="noopener noreferrer"&gt;https://hub.docker.com/r/klakegg/hugo&lt;/a&gt;. All Docker images containing &lt;code&gt;ext&lt;/code&gt; in its name use the extended edition of HUGO. We will use the Alpine based image.&lt;/p&gt;

&lt;p&gt;The Docker image contains the HUGO CLI and exposes the &lt;code&gt;hugo&lt;/code&gt; command as the ENTRYPOINT. Running the container and specifying the arguments for the hugo command, gives you all the options you would have when you have HUGO installed locally.&lt;/p&gt;

&lt;p&gt;For example, the following command would show the version of HUGO when HUGO is installed locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same command can be run using the Docker container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; klakegg/hugo:0.107.0-ext-alpine version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The --rm flag indicates the container is immediately disposed after finishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  NEW SITE
&lt;/h2&gt;

&lt;p&gt;The following command will generate a new HUGO site called 'test':&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/src klakegg/hugo:0.107.0-ext-alpine new site &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new folder 'test' will be created, which contains the generated site. The -v flag is used to create a volume from the present working directory (pwd). The &lt;code&gt;--format yaml&lt;/code&gt; at the end enables yaml config files instead of toml.&lt;/p&gt;

&lt;p&gt;To serve the site,  you first need to navigate to the root of the site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to serve the new site. (notice the added -p flag containing the port numbers):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 1313:1313 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/src klakegg/hugo:0.107.0-ext-alpine server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The site is built in-memory and will be hosted at &lt;a href="http://localhost:1313" rel="noopener noreferrer"&gt;http://localhost:1313&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You will see a 'Page not found' message when you visit the URL. Let's create an initial page by adding index.html to the layouts folder.&lt;/p&gt;

&lt;p&gt;layouts/index.html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;HUGO &lt;span class="err"&gt;&amp;amp;&lt;/span&gt; Docker demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;  
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt; 
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt; 
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello, world!!!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt; 
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt; 
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might need to restart the server to view the changes if your are working on Windows (I have a solution for that in a bit using docker-compose).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 1313:1313 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/src klakegg/hugo:0.107.0-ext-alpine server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you will be greeted with 'Hello World' when you visit &lt;a href="http://localhost:1313" rel="noopener noreferrer"&gt;http://localhost:1313&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  DOCKER-COMPOSE
&lt;/h2&gt;

&lt;p&gt;Now instead of running the server each time using &lt;code&gt;Docker run&lt;/code&gt;, we can add the server to a docker-compose.yml file and spin up the server with &lt;code&gt;docker compose up&lt;/code&gt;. File changes are automatically picked up:&lt;/p&gt;

&lt;p&gt;docker-compose.yml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;klakegg/hugo:0.107.0-ext-alpine&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server -D --poll 700ms&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.:/src"&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1313:1313"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The -D argument makes sure drafts are also build and served, this is what you usually want during development.&lt;br&gt;
The &lt;code&gt;poll&lt;/code&gt; flag is added for Windows users. In cases where Hugo CLI is running in a shared directory on a Linux VM on a Windows host the server is not detecting file changes from the host environment. The polling fixes this problem on Windows. More information &lt;a href="https://github.com/klakegg/docker-hugo/issues/61" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  EXPOSE HUGO CLI VIA PACKAGE.JSON
&lt;/h2&gt;

&lt;p&gt;Now that we have a convenient way of serving the site, we need a better way to access the HUGO CLI instead of calling Docker run each time. We will create a package.json file for that, and use the scrips section to make the HUGO CLI more accessible.&lt;/p&gt;

&lt;p&gt;Create a package.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expose the HUGO CLI by adding the following command to the scripts section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"hugo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cross-env-shell docker run --rm -v $INIT_CWD:/src klakegg/hugo:0.107.0-ext-alpine"&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;'cross-env-shell' is needed for a platform independent way of reading environments variables, such as the $INIT_CWD to get the current working directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i cross-env &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you call the HUGO CLI like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run hugo &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CLI arguments are placed after the &lt;code&gt;--&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;To build the website you can use the hugo command without arguments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run hugo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;server&lt;/code&gt; command as used earlier, builds the site in memory. The build command builds the site and stores the output in the ./public folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  CONCLUSION
&lt;/h2&gt;

&lt;p&gt;Now you are able to get started with HUGO without installing it on your machine. Hope this helps someone, it took me a few hours to figure this out, especially to get it to work on Windows. &lt;/p&gt;

</description>
      <category>hugo</category>
      <category>docker</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Setting up an Authorization Server with OpenIddict - Part VI - Refresh tokens</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Fri, 11 Dec 2020 13:14:13 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is part of a series called &lt;strong&gt;Setting up an Authorization Server with OpenIddict&lt;/strong&gt;. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the &lt;a href="https://dotnet.microsoft.com/apps/aspnet" rel="noopener noreferrer"&gt;ASPNET Core&lt;/a&gt; platform using &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid"&gt;Part I: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949"&gt;Part II: Create ASPNET project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp"&gt;Part III: Client Credentials Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;Part IV: Authorization Code Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j"&gt;Part V: OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part VI: Refresh tokens&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/authorization-server-openiddict" rel="noopener noreferrer"&gt;
        authorization-server-openiddict
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Authorization Server implemented with OpenIddict.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Access tokens typically have a short lifetime for security reasons. In this part we will enable the usage of refresh tokens. A refresh token allows an application to obtain a new access token without prompting the user. &lt;/p&gt;

&lt;h2&gt;
  
  
  Enable refresh tokens
&lt;/h2&gt;

&lt;p&gt;First we need to enable the refresh token flow in &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAuthorizationCodeFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequireProofKeyForCodeExchange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowClientCredentialsFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowRefreshTokenFlow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the token endpoint in the &lt;code&gt;AuthorizationController&lt;/code&gt; we need to handle refresh token requests, just like we handled client credentials requests, and requests to exchange authorization codes for access tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/connect/token"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Exchange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsAuthorizationCodeGrantType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsRefreshTokenGrantType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Retrieve the claims principal stored in the refresh token.&lt;/span&gt;
        &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, we only need to retrieve the ClaimsPrincipal from the refresh token, and sign in again, which will return a new access token.&lt;/p&gt;

&lt;p&gt;Finally, we need to allow the client to use refresh tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthorizationCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientCredentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RefreshToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefixes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a client wants to use refresh tokens, it needs to request the &lt;code&gt;offline_access&lt;/code&gt; scope. Just like we requested the &lt;code&gt;openid&lt;/code&gt; scope for identity tokens. Now, if we request a new access token with Postman, we will receive a refresh token together with the access token.&lt;/p&gt;

&lt;p&gt;After that, we can use the refresh token to request a new access token with Postman. Send a POST request to &lt;code&gt;/connect/token&lt;/code&gt; with a body containing the client credentials, grant type and of course the refresh token.&lt;/p&gt;

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

&lt;p&gt;In the response you will find a new access token and also a new refresh token. So, refresh tokens are automatically rotated, which is more secure than reusing the same refresh token.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>oauth2</category>
      <category>openiddict</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Setting up an Authorization Server with OpenIddict - Part V - OpenID Connect</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Fri, 11 Dec 2020 13:14:04 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is part of a series called &lt;strong&gt;Setting up an Authorization Server with OpenIddict&lt;/strong&gt;. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the &lt;a href="https://dotnet.microsoft.com/apps/aspnet" rel="noopener noreferrer"&gt;ASPNET Core&lt;/a&gt; platform using &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid"&gt;Part I: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949"&gt;Part II: Create ASPNET project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp"&gt;Part III: Client Credentials Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;Part IV: Authorization Code Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part V: OpenID Connect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669"&gt;Part VI: Refresh tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/authorization-server-openiddict" rel="noopener noreferrer"&gt;
        authorization-server-openiddict
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Authorization Server implemented with OpenIddict.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;OpenIddict implements the OpenID Connect protocol, which is an identity layer on top of the OAuth2 protocol. &lt;br&gt;
The implementation of the OpenID Connect protocol issues an extra token to the client application, called the &lt;code&gt;identity token&lt;/code&gt;. This token contains user profile information which can be used by client applications to identify the end-user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://leastprivilege.com/2016/12/14/optimizing-identity-tokens-for-size/" rel="noopener noreferrer"&gt;It's wise to keep your tokens small&lt;/a&gt;. Therefore, the OpenID Connect protocol offers the possibility to expose an userinfo endpoint from which clients can retrieve extra information about the end-user which is not stored in the identity token.&lt;/p&gt;

&lt;p&gt;In this part we will see how to leverage the OpenID Connect protocol and how to add an endpoint for querying extra user information.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enable OpenID Connect
&lt;/h2&gt;

&lt;p&gt;As said, OpenIddict has implemented the OpenID Connect protocol. You can test this by requesting an extra scope called &lt;code&gt;openid&lt;/code&gt; with Postman:&lt;/p&gt;

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

&lt;p&gt;In the response you will find an identity token together with the access token.&lt;/p&gt;

&lt;p&gt;To store extra profile information in the identity token, you can add claims in the &lt;code&gt;Authorize&lt;/code&gt; method in the &lt;code&gt;AuthorizationController&lt;/code&gt; when using the authorization code flow or add claims in the token endpoint if you are using the client credentials flow. Make sure to set the destination to &lt;code&gt;IdentityToken&lt;/code&gt;. Here's an example using a default claim called &lt;code&gt;email&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 'subject' claim which is required&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some claim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"some value"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SetDestinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destinations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"some@email"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SetDestinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destinations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdentityToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is also possible for a claim to have multiple destinations, so we can add a claim to the access token and the identity token if we want.&lt;/p&gt;

&lt;p&gt;You can view the content of the identity token on &lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Robin van der Knaap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"some@email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oi_au_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e5e12b78-ddea-42df-8350-5b7bda36b07b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"azp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"at_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iuzdujyas1p_1XjUuxtw9A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oi_tkn_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7f0ae652-c997-4425-bda5-6d024ea3acb3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1607684956&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:5001/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1607683756&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Userinfo endpoint
&lt;/h2&gt;

&lt;p&gt;Besides the identity token, the OpenID Connect protocols also allows for an url to get information about the user. To leverage the userinfo endpoint, we need to enable the endpoint and set the url in &lt;code&gt;Startup.cs&lt;/code&gt; first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAuthorizationEndpointUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/connect/authorize"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetTokenEndpointUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/connect/token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetUserinfoEndpointUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/connect/userinfo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAspNetCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableTokenEndpointPassthrough&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableAuthorizationEndpointPassthrough&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableUserinfoEndpointPassthrough&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you restart the authorization server and navigate to &lt;a href="https://localhost:5001/.well-known/openid-configuration" rel="noopener noreferrer"&gt;https://localhost:5001/.well-known/openid-configuration&lt;/a&gt;. You will see that the userinfo endpoint is added to public configuration file:&lt;/p&gt;

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

&lt;p&gt;Next step is to implement the userinfo endpoint. We can decide for ourselves which user info we want to share with clients. We will add the method to the &lt;code&gt;AuthorizationController&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthenticationSchemes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/connect/userinfo"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Userinfo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Occupation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;43&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user info endpoint can only be accessed when the client has a valid access token. This is why we added the &lt;code&gt;Authorize&lt;/code&gt; attribute. For this to work, we have to enable authorization in &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthorization&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we do a GET request to &lt;code&gt;/connect/userinfo&lt;/code&gt; and use a valid access token, the response looks something like this:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669"&gt;Next up&lt;/a&gt;, we will enable the usage of refresh tokens.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>oauth2</category>
      <category>openiddict</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Setting up an Authorization Server with OpenIddict - Part IV - Authorization Code Flow</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Fri, 11 Dec 2020 13:13:55 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is part of a series called &lt;strong&gt;Setting up an Authorization Server with OpenIddict&lt;/strong&gt;. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the &lt;a href="https://dotnet.microsoft.com/apps/aspnet" rel="noopener noreferrer"&gt;ASPNET Core&lt;/a&gt; platform using &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid"&gt;Part I: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949"&gt;Part II: Create ASPNET project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp"&gt;Part III: Client Credentials Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part IV: Authorization Code Flow&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j"&gt;Part V: OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669"&gt;Part VI: Refresh tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/authorization-server-openiddict" rel="noopener noreferrer"&gt;
        authorization-server-openiddict
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Authorization Server implemented with OpenIddict.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;In this part we will implement the &lt;a href="https://oauth.net/2/grant-types/authorization-code/" rel="noopener noreferrer"&gt;Authorization Code Flow with PKCE extension&lt;/a&gt;. This flow is the recommended flow for Single Page Applications (SPA's) and native/mobile applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure OpenIddict
&lt;/h2&gt;

&lt;p&gt;First we need to enable the Authorization Code Flow in &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenIddict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="p"&gt;...&lt;/span&gt;

        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAuthorizationCodeFlow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RequireProofKeyForCodeExchange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="p"&gt;...&lt;/span&gt;

            &lt;span class="n"&gt;options&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAuthorizationEndpointUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/connect/authorize"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetTokenEndpointUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/connect/token"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="p"&gt;...&lt;/span&gt;

            &lt;span class="n"&gt;options&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAspNetCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableTokenEndpointPassthrough&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableAuthorizationEndpointPassthrough&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call &lt;code&gt;AllowAuthorizationCodeFlow&lt;/code&gt; enables the flow, &lt;code&gt;RequireProofKeyForCodeExchange&lt;/code&gt; is called directly after that, this makes sure all clients are required to use PKCE (Proof Key for Code Exchange).&lt;/p&gt;

&lt;p&gt;The authorization code flow dictates that the user first &lt;strong&gt;authorizes&lt;/strong&gt; the client to make requests in the user's behalf. Therefore, we need to implement an authorization endpoint which returns an &lt;strong&gt;authorization code&lt;/strong&gt; to the client when the user allows it.&lt;/p&gt;

&lt;p&gt;The client can exchange the authorization code for an access token by calling the token endpoint we already created for the client credentials flow.&lt;/p&gt;

&lt;p&gt;First, we will create the authorization endpoint, the call to &lt;code&gt;EnableAuthorizationEndpointPassthrough&lt;/code&gt; in &lt;code&gt;Startup.cs&lt;/code&gt; allows us to implement the endpoint within a controller. &lt;/p&gt;

&lt;p&gt;After that we will make some minor adjustments to our token endpoint to allow clients to exchange authorization codes for access tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization endpoint
&lt;/h2&gt;

&lt;p&gt;We'll implement the authorization endpoint in the Authorization Controller, just like the token endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/connect/authorize"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/connect/authorize"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;IgnoreAntiforgeryToken&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOpenIddictServerRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The OpenID Connect request cannot be retrieved."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Retrieve the user principal stored in the authentication cookie.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CookieAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// If the user principal can't be extracted, redirect the user to the login page.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Succeeded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Challenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;authenticationSchemes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CookieAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuthenticationProperties&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;RedirectUri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathBase&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;QueryString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasFormContentType&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a new claims principal&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 'subject' claim which is required&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some claim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"some value"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SetDestinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destinations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claimsIdentity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claimsIdentity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Set requested scopes (this is not done automatically)&lt;/span&gt;
    &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetScopes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetScopes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="c1"&gt;// Signing in with the OpenIddict authentiction scheme trigger OpenIddict to issue a code (which can be exchanged for an access token)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;SignIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the Clients Credentials Flow, the Authorization Code Flow involves the end user for approval. Remember that we already implemented authentication in our project. So, in the authorize method we just determine if the user is already logged in, if not, we redirect the user to the login page.&lt;/p&gt;

&lt;p&gt;When the user is authenticated, a new claims principal is created which is used to sign in with OpenIddict authentication scheme.&lt;/p&gt;

&lt;p&gt;You can add claims to principal which will be added to the access token if the destination is set to &lt;code&gt;AccessToken&lt;/code&gt;. The &lt;code&gt;subject&lt;/code&gt; claim is required and is always added to the access token, you don't have to specify a destination for this claim.&lt;/p&gt;

&lt;p&gt;The scopes requested by the client are all given with the call &lt;code&gt;claimsPrincipal.SetScopes(request.GetScopes())&lt;/code&gt;, because we don't implement a consent screen in this example to keep things simple. When you do implement consent, this would be the place to filter the requested scopes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SignIn&lt;/code&gt; call triggers the OpenIddict middleware to send an authorization code which the client can exchange for an access token by calling the token endpoint.&lt;/p&gt;

&lt;p&gt;We need to alter the token endpoint also since we now support the Authorization Code Flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/connect/token"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Exchange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsClientCredentialsGrantType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsAuthorizationCodeGrantType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Retrieve the claims principal stored in the authorization code&lt;/span&gt;
        &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AuthenticateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The specified grant type is not supported."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The claims principal we created in the authorize method is stored in the authorization code, so we only need to grab the claims principal from the request and pass it to the &lt;code&gt;SignIn&lt;/code&gt; method. OpenIddict will respond with an access token.&lt;/p&gt;

&lt;p&gt;Now, let's see if we can request an access token with the Authorization Code Flow using Postman:&lt;/p&gt;

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

&lt;p&gt;This won't work because the client is not allowed to use the Authorization Code Flow and also we did not specify the &lt;code&gt;RedirectUris&lt;/code&gt; of the client. &lt;/p&gt;

&lt;p&gt;The redirect URI in the case of Postman is &lt;code&gt;https://oauth.pstmn.io/v1/callback&lt;/code&gt;. The authorization code is sent here after successful authentication.&lt;/p&gt;

&lt;p&gt;After updating, the Postman client in &lt;code&gt;TestData.cs&lt;/code&gt; should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;ClientSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postman-secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;RedirectUris&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://oauth.pstmn.io/v1/callback"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="n"&gt;Permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthorizationCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientCredentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefixes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is working correctly, you should be able to obtain an access token with Postman after authenticating yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note. A client secret is optional when configuring a client with OpenIddict, this is useful for public clients which aren't able to securely store a client secret. When the client secret is omitted from the configuration, you can also omit it from the request.&lt;br&gt;
The PKCE-enhanced Authorization Code Flow introduces a secret created by the calling application that can be verified by the authorization server (&lt;a href="https://auth0.com/docs/flows/authorization-code-flow-with-proof-key-for-code-exchange-pkce" rel="noopener noreferrer"&gt;source&lt;/a&gt;). However, PKCE doesn't replace client secrets. PKCE and client secrets are complementary and you should use them both when possible (typically, for server-side apps). (&lt;a href="https://twitter.com/kevin_chalet/status/1338130301739036673" rel="noopener noreferrer"&gt;Explained to me by the author of OpenIddict&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Congratulations, you have implemented the Authentication Code Flow with PKCE with OpenIddict! &lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j"&gt;Next up&lt;/a&gt;, we will demonstrate how to leverage the OpenID Connect protocol.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>oauth2</category>
      <category>openiddict</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Setting up an Authorization Server with OpenIddict - Part III - Client Credentials Flow</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Fri, 11 Dec 2020 13:13:50 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is part of a series called &lt;strong&gt;Setting up an Authorization Server with OpenIddict&lt;/strong&gt;. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the &lt;a href="https://dotnet.microsoft.com/apps/aspnet" rel="noopener noreferrer"&gt;ASPNET Core&lt;/a&gt; platform using &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid"&gt;Part I: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949"&gt;Part II: Create ASPNET project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part III: Client Credentials Flow&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;Part IV: Authorization Code Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j"&gt;Part V: OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669"&gt;Part VI: Refresh tokens&lt;/a&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/authorization-server-openiddict" rel="noopener noreferrer"&gt;
        authorization-server-openiddict
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Authorization Server implemented with OpenIddict.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;In this part we will add OpenIddict to the project and implement out first authorization flow: The &lt;a href="https://oauth.net/2/grant-types/client-credentials/" rel="noopener noreferrer"&gt;Client Credentials Flow&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add OpenIddict packages
&lt;/h2&gt;

&lt;p&gt;First, we need to install the OpenIddict NuGet packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package OpenIddict
dotnet add package OpenIddict.AspNetCore
dotnet add package OpenIddict.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.InMemory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides the main library we also installed the &lt;code&gt;OpenIddict.AspNetCore&lt;/code&gt; package, this package enables the integration of OpenIddict in an ASPNET Core host.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;OpenIddict.EntityFrameworkCore&lt;/code&gt; package enables Entity Framework Core support. For now we will work with an in-memory implementation, for which we use the package &lt;code&gt;Microsoft.EntityFrameworkCore.InMemory&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup OpenIddict
&lt;/h2&gt;

&lt;p&gt;We will start with what is minimal required to get OpenIddict up and running. At least one OAuth 2.0/OpenID Connect flow must be enabled. We choose to enable the the &lt;a href="https://oauth.net/2/grant-types/client-credentials/" rel="noopener noreferrer"&gt;Client Credentials Flow&lt;/a&gt;, which is suitable for machine-to-machine applications. In the next part of this series we will implement the &lt;a href="https://oauth.net/2/grant-types/authorization-code/" rel="noopener noreferrer"&gt;Authorization Code Flow with PKCE&lt;/a&gt; which is the recommended flow for Single Page Applications (SPA) and native/mobile applications.&lt;/p&gt;

&lt;p&gt;Start to make the following changes to &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Configure the context to use an in-memory store.&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseInMemoryDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="c1"&gt;// Register the entity sets needed by OpenIddict.&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseOpenIddict&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenIddict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// Register the OpenIddict core components.&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Configure OpenIddict to use the EF Core stores/models.&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UseDbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c1"&gt;// Register the OpenIddict server components.&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowClientCredentialsFlow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;options&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetTokenEndpointUris&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/connect/token"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Encryption and signing of tokens&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEphemeralEncryptionKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEphemeralSigningKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Register scopes (permissions)&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterScopes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAspNetCore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableTokenEndpointPassthrough&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;            
        &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, the DbContext is registered in the &lt;code&gt;ConfigureServices&lt;/code&gt; method. OpenIddict natively supports Entity Framework Core, Entity Framework 6 and MongoDB out-of-the-box, and you can also provide your own stores. &lt;br&gt;
In this example, we will use Entity Framework Core, and we will use an in-memory database. The &lt;code&gt;options.UseOpenIdDict&lt;/code&gt; call registers the entity sets needed by OpenIddict.&lt;/p&gt;

&lt;p&gt;Next, OpenIddict itself is registered. The &lt;code&gt;AddOpenIddict()&lt;/code&gt; call registers the OpenIddict services and returns a &lt;code&gt;OpenIddictBuilder&lt;/code&gt; class with which we can configure OpenIddict. &lt;/p&gt;

&lt;p&gt;The core components are registered first. OpenIddict is instructed to use Entity Framework Core, and use the aforementioned DbContext.&lt;/p&gt;

&lt;p&gt;Next, the server components are registered and the Client Credentials Flow is enabled. For this flow to work, we need to register a token endpoint. We need to implement this endpoint ourselves. We'll do this later.&lt;/p&gt;

&lt;p&gt;For OpenIddict to be able to encrypt and sign tokens we need to register two keys, one for encrypting and one for signing. In this example we'll use ephemeral keys. Ephemeral keys are automatically discarded when the application shuts down and payloads signed or encrypted using these key are therefore automatically invalidated. This method should only be used during development. On production, using a X.509 certificate is recommended.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;RegisterScopes&lt;/code&gt; defines which scopes (permissions) are supported. In this case we have one scope called &lt;code&gt;api&lt;/code&gt;, but the authorization server can support multiple scopes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;UseAspNetCore()&lt;/code&gt; call is used to setup AspNetCore as a host for OpenIddict. We also call &lt;code&gt;EnableTokenEndpointPassthrough&lt;/code&gt; otherwise requests to our future token endpoint are blocked.&lt;/p&gt;

&lt;p&gt;To check if OpenIddict is properly configured we can start the application and navigate to: &lt;a href="https://localhost:5001/.well-known/openid-configuration" rel="noopener noreferrer"&gt;https://localhost:5001/.well-known/openid-configuration&lt;/a&gt;, the response should be something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:5001/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:5001/connect/token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jwks_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://localhost:5001/.well-known/jwks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"grant_types_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"client_credentials"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scopes_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"openid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"api"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claims_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"sub"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id_token_signing_alg_values_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"RS256"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_types_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_endpoint_auth_methods_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"client_secret_basic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"client_secret_post"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"claims_parameter_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_parameter_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request_uri_parameter_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this guide, we will be using &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; to test the authorization server, but you could just as easily use another tool.&lt;/p&gt;

&lt;p&gt;Below you find an example authorization request using Postman. Grant Type is the Client Credentials Flow. We specify the access token url, a client id and secret to authenticate our client. We also request access to the &lt;code&gt;api&lt;/code&gt; scope. &lt;/p&gt;

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

&lt;p&gt;If we request the token, the operation will fail: The client_id is invalid. This makes sense, because we haven't registered any clients yet with our Authorization Server. &lt;br&gt;
We can create a client by adding it to the database. For that purpose we create a class called &lt;code&gt;TestData&lt;/code&gt;. The test data implements the &lt;code&gt;IHostedService&lt;/code&gt; interface, which enables us to execute the generation of test data in &lt;code&gt;Startup.cs&lt;/code&gt; when the application starts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.EntityFrameworkCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenIddict.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestData&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IHostedService&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;_serviceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceProvider&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DbContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureCreatedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOpenIddictApplicationManager&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindByClientIdAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenIddictApplicationDescriptor&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ClientSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"postman-secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Postman"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;Permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientCredentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

                        &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefixes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"api"&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A client is registered in the test data. The client id and secret are used to authenticate the client with the authorization server. The permissions determine what this client's options are.&lt;br&gt;
In this case we allow for the client to use the Client Credentials Flow, access the token endpoint and allow the client to request the &lt;code&gt;api&lt;/code&gt; scope.&lt;/p&gt;

&lt;p&gt;Register the test data service in &lt;code&gt;Startup.cs&lt;/code&gt;, so it is executed when the application starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we try to obtain an access token with Postman again, the request will still fail. This is because we haven't created the token endpoint yet. We'll do that now. &lt;/p&gt;

&lt;p&gt;Create a new controller called &lt;code&gt;AuthorizationController&lt;/code&gt;, we  will host the endpoint here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Security.Claims&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenIddict.Abstractions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OpenIddict.Server.AspNetCore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthorizationController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"~/connect/token"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Exchange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOpenIddictServerRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt;
                          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The OpenID Connect request cannot be retrieved."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;ClaimsPrincipal&lt;/span&gt; &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsClientCredentialsGrantType&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Note: the client credentials are automatically validated by OpenIddict:&lt;/span&gt;
                &lt;span class="c1"&gt;// if client_id or client_secret are invalid, this action won't be invoked.&lt;/span&gt;

                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="c1"&gt;// Subject (sub) is a required field, we use the client id as the subject identifier here.&lt;/span&gt;
                &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

                &lt;span class="c1"&gt;// Add some claim, don't forget to add destination otherwise it won't be added to the access token.&lt;/span&gt;
                &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddClaim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-claim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"some-value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenIddictConstants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Destinations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="n"&gt;claimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetScopes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetScopes&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The specified grant type is not supported."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;SignIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OpenIddictServerAspNetCoreDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One action is implemented, &lt;code&gt;Exchange&lt;/code&gt;. This action is used by all flows, not only the Client Credentials Flow, to obtain an access token.&lt;/p&gt;

&lt;p&gt;In the case of the Client Credentials Flow, the token is issued based on the client credentials. In the case of Authorization Code Flow, the same endpoint is used but then to exchange an authorization code for a token. We'll see that in &lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;part IV&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For now, we need to focus on the Client Credentials Flow. When the request enters the &lt;code&gt;Exchange&lt;/code&gt; action, the client credentials (ClientId and ClientSecret) are already validated by OpenIddict. So we don't need to authenticate the request, we only have to create a claims principal and use that to sign in.&lt;/p&gt;

&lt;p&gt;The claims principal is not the same as we used in the Account controller, that one was based on the Cookie authentication handler and is only used within the context of the Authorization server itself to determine if the user has been authenticated or not.&lt;/p&gt;

&lt;p&gt;The claims principal we have to create is based on the &lt;code&gt;OpenIddictServerAspNetCoreDefaults.AuthenticationScheme&lt;/code&gt;. This way, when we call &lt;code&gt;SignIn&lt;/code&gt; at the end of this method, the OpenIddict middleware will handle the sign in and return an access token as response to the client.&lt;/p&gt;

&lt;p&gt;The claims defined in the claims principal are included in the access token only when we specify the destination. The &lt;code&gt;some-value&lt;/code&gt; claim in the example will be added to the access token.&lt;br&gt;
The &lt;code&gt;subject&lt;/code&gt; claim is required and you don't need to specify a destination, it will be included in the access token anyway. &lt;/p&gt;

&lt;p&gt;We also grant all the requested scopes by calling &lt;code&gt;claimsPrincipal.SetScopes(request.GetScopes());&lt;/code&gt;. OpenIddict has already checked if the requested scopes are allowed (in general and for the current client). The reason why we have to add the scopes manually here is that we are able to filter the scopes granted here if we want to.&lt;/p&gt;
&lt;h2&gt;
  
  
  One token to rule them all
&lt;/h2&gt;

&lt;p&gt;Let's try to obtain an access token with Postman again, this time it should work. &lt;/p&gt;

&lt;p&gt;As of v3 of OpenIddict, the access token is in Jason Web Token (JWT) format by default. This enables us to examine the token with &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt;. (Thanks &lt;a href="https://auth0.com/" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;, for hosting that service!)&lt;/p&gt;

&lt;p&gt;One problem, the token is not only signed, but also encrypted. OpenIddict encrypts the access token by default. We can disable this encryption when configuring OpenIddict in &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Encryption and signing of tokens&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEphemeralEncryptionKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddEphemeralSigningKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisableAccessTokenEncryption&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when we restart our authorization server and request a new token. Paste the token to &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt; and view the content of the token:&lt;/p&gt;

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

&lt;p&gt;As you can see the client id &lt;code&gt;postman&lt;/code&gt; is set as subject &lt;code&gt;sub&lt;/code&gt;. Also the &lt;code&gt;some-claim&lt;/code&gt; claim is added to the access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Congratulations, you have implemented the &lt;code&gt;Client Credentials Flow&lt;/code&gt; with OpenIddict!&lt;/p&gt;

&lt;p&gt;As you may have noticed, the login page is unused by the Client Credentials Flow. This flow exchanges the client credentials for a token immediately, which is suitable for machine-to-machine applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;Next up&lt;/a&gt;, we will implement the &lt;code&gt;Authorization Code Flow with PKCE&lt;/code&gt;, which is the recommended flow for single page applications (SPA) and mobile apps. This flow will involve the user, so our login page will come in to play.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>oauth2</category>
      <category>openiddict</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Setting up an Authorization Server with OpenIddict - Part II - Create ASPNET project</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Fri, 11 Dec 2020 13:13:44 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is part of a series called &lt;strong&gt;Setting up an Authorization Server with OpenIddict&lt;/strong&gt;. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the &lt;a href="https://dotnet.microsoft.com/apps/aspnet" rel="noopener noreferrer"&gt;ASPNET Core&lt;/a&gt; platform using &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid"&gt;Part I: Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part II: Create ASPNET project&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp"&gt;Part III: Client Credentials Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;Part IV: Authorization Code Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j"&gt;Part V: OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669"&gt;Part VI: Refresh tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/authorization-server-openiddict" rel="noopener noreferrer"&gt;
        authorization-server-openiddict
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Authorization Server implemented with OpenIddict.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;In this part we will create an ASPNET Core project which serves as a minimal setup for our authorization server. We will use MVC to serve pages and we will add authentication to the project, including a basic login form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new empty ASPNET project.
&lt;/h2&gt;

&lt;p&gt;As was said in the previous article, an authorization server is just another web application. The following content will guide you through setting up an ASPNET Core application with a username-password login. I choose not to use &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity" rel="noopener noreferrer"&gt;ASPNET Core Identity&lt;/a&gt; to keep things really simple. Basically every username-password combination will work. &lt;/p&gt;

&lt;p&gt;Let's start with creating a new web application called &lt;code&gt;AuthorizationServer&lt;/code&gt; using the &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new#web" rel="noopener noreferrer"&gt;ASP.NET Core Empty template&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new web --name AuthorizationServer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will work with just this project, and we will not add a solution file in this guide. &lt;/p&gt;

&lt;p&gt;OpenIddict requires us to work with the &lt;code&gt;https&lt;/code&gt; protocol, even when developing locally. To make sure the local certificate is trusted, you will have to run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet dev-certs https --trust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows the certificate will be added to the certificate store and on OSX to the keychain. On Linux there isn't a standard way across distros to trust the certificate. Read more about this subject in &lt;a href="https://www.hanselman.com/blog/developing-locally-with-aspnet-core-under-https-ssl-and-selfsigned-certs" rel="noopener noreferrer"&gt;Hanselman's blog article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Start the application to see if everything works as expected&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run --project AuthorizationServer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;a href="https://localhost:5001" rel="noopener noreferrer"&gt;https://localhost:5001&lt;/a&gt;. You should see the &lt;code&gt;Hello World!&lt;/code&gt; in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  MVC
&lt;/h2&gt;

&lt;p&gt;We have created a project based on the ASPNET Core Empty template. This is a very minimal template. I have done this intentionally, because I like to have as little 'noise' in my project as possible to keep things clear and simple.&lt;/p&gt;

&lt;p&gt;Downside of using this template is we have to add &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/overview" rel="noopener noreferrer"&gt;MVC&lt;/a&gt; ourselves. First, we need to enable MVC by altering the &lt;code&gt;Startup.cs&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllersWithViews&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsDevelopment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseDeveloperExceptionPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseStaticFiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapDefaultControllerRoute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MVC is configured by calling &lt;code&gt;services.AddControllersWithViews()&lt;/code&gt;. Endpoints are setup to use default routing. We also enabled the serving of static files, we need this to serve our style sheets out of the wwwroot folder.&lt;/p&gt;

&lt;p&gt;Now, let's create the controllers, views and view models. Start with adding the following folder structure inside the project folder (mind the casing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Controllers
/Views
/Views/Home  
/Views/Shared
/ViewModels
/wwwroot
/wwwroot/css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Layout
&lt;/h2&gt;

&lt;p&gt;The first item we add is a layout file called &lt;code&gt;_Layout.cshtml&lt;/code&gt; to the &lt;code&gt;Views/Shared&lt;/code&gt; folder. This file defines the general layout of the application, and also loads &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap&lt;/a&gt; and jQuery from a CDN. jQuery is a dependency of Bootstrap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"IE=edge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0, shrink-to-fit=no"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;OpenIddict - Authorization Server&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"~/css/site.css"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container-sm mt-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
                Authorization Server
            &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"col-xs-12 col-md-8 col-xl-4 offset-md-2 offset-xl-4 text-center mb-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            @RenderBody()
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://code.jquery.com/jquery-3.5.1.slim.min.js"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the layout and views to work, we need to add two files to the &lt;code&gt;\Views&lt;/code&gt; folder: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;_ViewStart.cshtml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Layout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"_Layout"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_ViewImports.cshtml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@addTagHelper&lt;/span&gt; &lt;span class="p"&gt;*,&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mvc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TagHelpers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Home page
&lt;/h2&gt;

&lt;p&gt;Add a basic &lt;code&gt;HomeController&lt;/code&gt; to the &lt;code&gt;/Controllers&lt;/code&gt; folder, which has the sole purpose of serving our home page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;Index.cshtml&lt;/code&gt; to the &lt;code&gt;Views/Home&lt;/code&gt; folder, which is served by the HomeController:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;MVC is working&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Style sheet
&lt;/h2&gt;

&lt;p&gt;Last but not least, we need some styling. Add a style sheet called &lt;code&gt;site.css&lt;/code&gt; to the &lt;code&gt;wwwroot\css&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.input-validation-error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;darkred&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.form-control&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;lightgray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0.9rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.form-control&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;lightgray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.form-control.form-control-last&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.form-control&lt;/span&gt;&lt;span class="nd"&gt;::placeholder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.form-control.input-validation-error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;darkred&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some style rules are already added to the style sheet anticipating the login form we are going to create later.&lt;/p&gt;

&lt;p&gt;If you want to use SASS, or customize Bootstrap with SASS, check &lt;a href="https://dev.to/robinvanderknaap/setting-up-bootstrap-4-sass-with-aspnet-core-4ff6"&gt;my article&lt;/a&gt; about setting up Bootstrap SASS with ASPNET. &lt;/p&gt;

&lt;p&gt;Let's run the application and see if everything is working, you should see something like this in your browser:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Enable authentication
&lt;/h2&gt;

&lt;p&gt;In ASP.NET Core, &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication" rel="noopener noreferrer"&gt;authentication&lt;/a&gt; is handled by the &lt;code&gt;IAuthenticationService&lt;/code&gt;. The authentication service uses authentication handlers to complete authentication-related actions.&lt;/p&gt;

&lt;p&gt;The authentication handlers are registered during startup and their configuration options are called "schemes". Authentication schemes are specified by registering authentication services in &lt;code&gt;Startup.ConfigureServices&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For this project we will use &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie" rel="noopener noreferrer"&gt;cookie authentication&lt;/a&gt;, so we need to register the Cookie authentication scheme in the &lt;code&gt;ConfigureServices&lt;/code&gt; method in &lt;code&gt;Startup.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ConfigureServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddControllersWithViews&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CookieAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CookieAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoginPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/account/login"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The login path is set to &lt;code&gt;/account/login&lt;/code&gt;, we will implement this endpoint shortly.&lt;/p&gt;

&lt;p&gt;The authentication middleware, which uses the registered authentication schemes, is added by calling the &lt;code&gt;UseAuthentication&lt;/code&gt; extension method on the app's &lt;code&gt;IApplicationBuilder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseRouting&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAuthentication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseEndpoints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoints&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapDefaultControllerRoute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call to &lt;code&gt;UseAuthentication&lt;/code&gt; is made after the call to &lt;code&gt;UseRouting&lt;/code&gt;, so that route information is available for authentication decisions, but before &lt;code&gt;UseEndpoints&lt;/code&gt;, so that users are authenticated before accessing the endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Login page
&lt;/h2&gt;

&lt;p&gt;Now that we have authentication enabled, we will need a login page to authenticate users. &lt;/p&gt;

&lt;p&gt;First, create the Login view model containing the information we need to authenticate the user. Make sure to put this file in the &lt;code&gt;ViewModels&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer.ViewModels&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginViewModel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Username&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Password&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ReturnUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a folder named &lt;code&gt;Account&lt;/code&gt; in the &lt;code&gt;Views&lt;/code&gt; folder and add the login view, &lt;code&gt;Login.cshtml&lt;/code&gt;,  containing the login form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@model AuthorizationServer.ViewModels.LoginViewModel
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;autocomplete=&lt;/span&gt;&lt;span class="s"&gt;"off"&lt;/span&gt; &lt;span class="na"&gt;asp-route=&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"ReturnUrl"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control form-control-lg"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Username"&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Username"&lt;/span&gt; &lt;span class="na"&gt;autofocus&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control form-control-lg form-control-last"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-dark btn-block mt-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we add the &lt;code&gt;AccountController&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Generic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Security.Claims&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer.ViewModels&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authentication&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authentication.Cookies&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Authorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;AuthorizationServer.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AllowAnonymous&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;returnUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ViewData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ReturnUrl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;returnUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AllowAnonymous&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ValidateAntiForgeryToken&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoginViewModel&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ViewData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ReturnUrl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReturnUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ClaimTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;

                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;claimsIdentity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CookieAuthenticationDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SignInAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ClaimsPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claimsIdentity&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsLocalUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReturnUrl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReturnUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;RedirectToAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HomeController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Home"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;View&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SignOutAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;RedirectToAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HomeController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Home"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what happens here?&lt;br&gt;
We have two login actions (GET and POST) on the account controller, both allow anonymous requests, otherwise nobody would be able to login. &lt;/p&gt;

&lt;p&gt;The GET action serves the login form we just created. We have an optional query parameter &lt;code&gt;returlUrl&lt;/code&gt; which we store in &lt;code&gt;ViewData&lt;/code&gt;, so we can use this to redirect the user after a successful login.&lt;/p&gt;

&lt;p&gt;The POST action is more interesting. First the &lt;code&gt;ModelState&lt;/code&gt; is validated. That means that a username and a password are required. We do not check the credentials here, any combination is valid in this example. Normally, this would be the place where you check the credentials against your database. &lt;/p&gt;

&lt;p&gt;When the ModelState is valid, a claims identity is constructed. We add one claim, the name of the user. Beware, we specify the cookie authentication scheme (&lt;code&gt;CookieAuthenticationDefaults.AuthenticationScheme&lt;/code&gt;) when creating the claims identity. This is basically a string, and maps to the authentication scheme we defined in the &lt;code&gt;Startup.cs&lt;/code&gt; class when setting up the cookie authentication.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SignInAsync&lt;/code&gt; method is an extension method which calls the AuthenticationService which calls the CookieAuthenticationHandler because that's the scheme we specified when creating the claims identity.&lt;/p&gt;

&lt;p&gt;After signing in we need to redirect the user. If a return url is specified, we check if it's a local url to &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/security/preventing-open-redirects" rel="noopener noreferrer"&gt;prevent open redirect attacks&lt;/a&gt; before redirecting. Otherwise the user is redirected to the home page.&lt;/p&gt;

&lt;p&gt;The last action, Logout, calls the authentication service to sign out the user. The authentication service will call the authentication middleware, in our case the cookie authentication middleware, to sign out the user.&lt;/p&gt;
&lt;h2&gt;
  
  
  Update home page
&lt;/h2&gt;

&lt;p&gt;Update the home page (&lt;code&gt;Views/Home/Index.cshtml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@using Microsoft.AspNetCore.Authentication

@if (User.Identity.IsAuthenticated)
{
    var authenticationResult = await Context.AuthenticateAsync();
    var issued = authenticationResult.Properties.Items[".issued"];
    var expires = authenticationResult.Properties.Items[".expires"];
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You are signed in as&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;@User.Identity.Name&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;hr/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dl&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dt&amp;gt;&lt;/span&gt;Issued&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dd&amp;gt;&lt;/span&gt;@issued&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dt&amp;gt;&lt;/span&gt;Expires&lt;span class="nt"&gt;&amp;lt;/dt&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dd&amp;gt;&lt;/span&gt;@expires&lt;span class="nt"&gt;&amp;lt;/dd&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dl&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;hr/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-dark"&lt;/span&gt; &lt;span class="na"&gt;asp-controller=&lt;/span&gt;&lt;span class="s"&gt;"Account"&lt;/span&gt; &lt;span class="na"&gt;asp-action=&lt;/span&gt;&lt;span class="s"&gt;"Logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign out&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}

@if (!User.Identity.IsAuthenticated)
{
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You are not signed in&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-sm btn-dark"&lt;/span&gt; &lt;span class="na"&gt;asp-controller=&lt;/span&gt;&lt;span class="s"&gt;"Account"&lt;/span&gt; &lt;span class="na"&gt;asp-action=&lt;/span&gt;&lt;span class="s"&gt;"Login"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign in&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the user is authenticated we display the user name, information about the current session and a sign-out button. When the user is not authenticated, we show a sign-in button which navigates the user to the login form.&lt;/p&gt;

&lt;p&gt;Start the application, you should see a sign-in button on the home page:&lt;/p&gt;

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

&lt;p&gt;When you click &lt;code&gt;Sign in&lt;/code&gt; you should navigate to the login form:&lt;/p&gt;

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

&lt;p&gt;To sign in, just fill in random credentials, everything but an empty value is fine. &lt;br&gt;
If everything works correctly you should be redirected to the home page which shows you are signed in:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Currently, we have a basic ASPNET Core project running with authentication implemented, nothing fancy so far. &lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp"&gt;Next up&lt;/a&gt;, we will add OpenIddict to the project and implement the Client Credentials Flow.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>oauth2</category>
      <category>openiddict</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Setting up an Authorization Server with OpenIddict - Part I - Introduction</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Fri, 11 Dec 2020 13:13:29 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-i-introduction-4jid</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article is part of a series called &lt;strong&gt;Setting up an Authorization Server with OpenIddict&lt;/strong&gt;. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the &lt;a href="https://dotnet.microsoft.com/apps/aspnet" rel="noopener noreferrer"&gt;ASPNET Core&lt;/a&gt; platform using &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part I: Introduction&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949"&gt;Part II: Create ASPNET project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iii-client-credentials-flow-55lp"&gt;Part III: Client Credentials Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-iv-authorization-code-flow-3eh8"&gt;Part IV: Authorization Code Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-v-openid-connect-a8j"&gt;Part V: OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-vi-refresh-tokens-5669"&gt;Part VI: Refresh tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/authorization-server-openiddict" rel="noopener noreferrer"&gt;
        authorization-server-openiddict
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Authorization Server implemented with OpenIddict.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





&lt;p&gt;Setting up an authorization server allows you to support token-based authentication and authorization. It also allows you to authenticate users for all your applications in one central place, Single Sign-On(SSO). &lt;/p&gt;

&lt;p&gt;An authorization server can offer one or multiple authentication methods simultaneously, like local username-password but also external identity providers such as Google, Facebook or ADFS.&lt;/p&gt;

&lt;p&gt;By implementing OAuth2 and OpenID Connect you are able to facilitate multiple authorization scenario's: Web applications, desktop applications, machine-to-machine communication and even authorization for living room devices. Both Oauth2 and OpenID Connect are industry standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  IdentityServer
&lt;/h2&gt;

&lt;p&gt;For over a decade the go-to project in .NET for implementing a secure token service and later OAuth2 + OpenID Connect was &lt;a href="https://identityserver.io/" rel="noopener noreferrer"&gt;IdentityServer&lt;/a&gt;. Lately, the creators/maintainers of IdentityServer &lt;a href="https://leastprivilege.com/2020/10/01/the-future-of-identityserver/" rel="noopener noreferrer"&gt;decided to dual license future versions of IdentityServer&lt;/a&gt;. Now unless you are working on an open-source project you will have to pay for a commercial license.&lt;/p&gt;

&lt;p&gt;Although, paying for good software shouldn't be an issue for a lot of projects or companies, I went out to look for an alternative anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenIddict
&lt;/h2&gt;

&lt;p&gt;An alternative for IdentityServer is &lt;a href="https://github.com/openiddict/openiddict-core" rel="noopener noreferrer"&gt;OpenIddict&lt;/a&gt;. OpenIddict provides a solution to implement an OpenID Connect server in any ASP.NET Core 2.1, 3.1 and 5.0 application, and starting with OpenIddict 3.0, any ASP.NET 4.x or OWIN application too.&lt;/p&gt;

&lt;p&gt;OpenIddict is little bit more low-level than IdentityServer. Identity Server gives you a running solution out-of-the-box,  where for OpenIddict to work you need to implement some details yourself, like creating claim identities and setting up the correct endpoints. &lt;/p&gt;

&lt;p&gt;In this article we will use OpenIddict to implement our Authorization Server. We will implement the &lt;a href="https://oauth.net/2/grant-types/client-credentials" rel="noopener noreferrer"&gt;Client Credentials Flow&lt;/a&gt;, the &lt;a href="https://oauth.net/2/grant-types/authorization-code" rel="noopener noreferrer"&gt;Authorization Code Flow&lt;/a&gt; and setup &lt;a href="https://oauth.net/2/grant-types/refresh-token/" rel="noopener noreferrer"&gt;refresh tokens&lt;/a&gt; as an example. We will also demonstrate how to leverage OpenID Connect to retrieve user information.&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth2 and OpenID Connect
&lt;/h2&gt;

&lt;p&gt;OAuth2 and OpenID Connect can be confusing at times, at least to me it is. &lt;/p&gt;

&lt;p&gt;It helped me to view an Authorization Server as just another web application. It's an application where you can login with a username-password combination or with some other external provider. Up to this point, nothing is different from any other web applications we have build. &lt;/p&gt;

&lt;p&gt;After setting up authentication we can implement OAuth2 and OpenID Connect flows (this is where OpenIddict comes into play). These flows are leveraged by other applications (clients), basically asking 'can I do some stuff on behalf of this user?'.&lt;/p&gt;

&lt;p&gt;To answer this question, users are first redirected to the Authorization Server by the clients. The user will authenticate himself if not already authenticated (SSO). After that, the user is asked if the client is allowed to make requests on behalf of the user. If yes, the external application receives an access token to make requests in the user's behalf. &lt;/p&gt;

&lt;p&gt;Besides the access token (OAuth2), a separate identity token (OpenID Connect) can be issued. The identity token can be used by clients to extract user information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/robinvanderknaap/setting-up-an-authorization-server-with-openiddict-part-ii-create-aspnet-project-4949"&gt;next article&lt;/a&gt; we will start by creating a new ASPNET Core project and implement authentication as the first step toward a full-blown Authorization Server.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>oauth2</category>
      <category>openiddict</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Setting up Bootstrap 4 SASS with ASPNET Core</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Sat, 17 Oct 2020 14:15:31 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/setting-up-bootstrap-4-sass-with-aspnet-core-4ff6</link>
      <guid>https://dev.to/robinvanderknaap/setting-up-bootstrap-4-sass-with-aspnet-core-4ff6</guid>
      <description>&lt;p&gt;The default ASPNET MVC template comes with &lt;a href="https://getbootstrap.com/" rel="noopener noreferrer"&gt;Bootstrap 4&lt;/a&gt; out-of-the-box. This is an already compiled version of Bootstrap. To &lt;a href="https://getbootstrap.com/docs/4.5/getting-started/theming/" rel="noopener noreferrer"&gt;customize&lt;/a&gt; the look and feel of Bootstrap elements, you need be able to override the default values of Bootstrap and compile Bootstrap from the SASS source.&lt;/p&gt;

&lt;p&gt;Setting up Bootstrap with ASPNET Core was not that obvious to me. I've tried using the &lt;a href="https://www.nuget.org/packages/bootstrap.sass/" rel="noopener noreferrer"&gt;Bootstrap.sass&lt;/a&gt; package, but this wasn't working for me. NuGet is not equipped as a Frontend Package manager &lt;a href="https://github.com/twbs/bootstrap/issues/16692#issuecomment-117922208" rel="noopener noreferrer"&gt;so it seems&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This articles describes how to setup Bootstrap 4 SASS in your ASPNET Core project using the following techniques:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NPM as package manager for frontend packages&lt;/li&gt;
&lt;li&gt;Webpack for compiling SASS files and creating and minifying the CSS and JS bundles.&lt;/li&gt;
&lt;li&gt;Setup Webpack as build task in .csproj file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  New ASPNET MVC project
&lt;/h2&gt;

&lt;p&gt;Create a new ASPNET MVC project with your favorite IDE or use terminal/cmd/powershell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new mvc --name AspNetBootstrapSass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run the project from your IDE or from the commandline&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd AspNetBootstrapSass
dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If everything is working correctly you should see the default ASPNET welcome page in your browser.&lt;/p&gt;
&lt;h2&gt;
  
  
  Remove distribution version of Bootstrap
&lt;/h2&gt;

&lt;p&gt;A distribution version of Bootstrap is already included in the template. However, we want to compile Bootstrap ourselves from the SASS source. Therefore, we will first remove Bootstrap (and also jquery) from the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remove &lt;code&gt;wwwroot/lib/bootstrap&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Remove &lt;code&gt;wwwroot/lib/jquery&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remove the links to all stylesheets in &lt;code&gt;Views/Shared/_Layout.cshtml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /&amp;gt;
&amp;lt;link rel="stylesheet" href="~/css/site.css" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Replaced them with a link to our CSS bundle, which we will create later:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" href="~/dist/bundle.css" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Remove Bootstrap, jQuery and site.js scripts:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script src="~/lib/jquery/dist/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="~/js/site.js" asp-append-version="true"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Replace them with our yet to be created javascript bundle:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script src="~/dist/bundle.js" asp-append-version="true"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Add frontend packages with NPM
&lt;/h2&gt;

&lt;p&gt;We will be using &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;NPM&lt;/a&gt; to import the packages we need. I'm assuming NodeJS and NPM are already installed on your system. Add the following &lt;code&gt;package.json&lt;/code&gt; to the project root folder.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Package.json&lt;/code&gt; contains two dependencies: &lt;code&gt;Bootstrap&lt;/code&gt; and &lt;code&gt;jQuery&lt;/code&gt;, jQuery is a dependency of Bootstrap. Webpack and a number of SASS related preprocessors are included as dev dependencies. We will need those to compile the SASS sources.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Package.json&lt;/code&gt; also contains some scripts, &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;production&lt;/code&gt;, which we will use later to start the Webpack build process.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npm install&lt;/code&gt; to install all packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We now have bootstrap in the node_modules folder (never add this folder to version control). We can now reference the bootstrap.scss file from our custom SCSS file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup SASS files
&lt;/h2&gt;

&lt;p&gt;We already have a &lt;code&gt;css&lt;/code&gt; folder in our wwwroot. Let's rename this to &lt;code&gt;scss&lt;/code&gt;. Also rename &lt;code&gt;site.css&lt;/code&gt; to &lt;code&gt;site.scss&lt;/code&gt;. Remember, a CSS file is always a valid SCSS file.&lt;/p&gt;

&lt;p&gt;Also create a file called &lt;code&gt;_variables.scss&lt;/code&gt;. This will contain our Bootstrap overrides (the background color and some theme colors are overridden as an example). It needs to be imported &lt;strong&gt;before&lt;/strong&gt; Bootstrap is imported.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Add the following at the top of site.scss&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;What is happening here? We are importing &lt;code&gt;_variables.scss&lt;/code&gt; containing our Bootstrap overrides. After that we are importing Bootstrap itself. Everything below that is our own custom CSS/SCSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Webpack
&lt;/h2&gt;

&lt;p&gt;To create the CSS and JS bundles we referenced in &lt;code&gt;_Layout.cshtml&lt;/code&gt; we need to configure Webpack. Add the following &lt;code&gt;webpack.config.js&lt;/code&gt; to the project root folder&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I'm not going to dive into the specific Webpack details here. Important is that in production mode, CSS and JS bundles are minified, in development mode the bundles are still readable.&lt;br&gt;&lt;br&gt;
To run Webpack, we can call one of the scripts we defined in &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The result can be found in &lt;code&gt;wwwroot\dist&lt;/code&gt;. We find 2 files: &lt;code&gt;bundle.css&lt;/code&gt; and &lt;code&gt;bundle.js&lt;/code&gt;. We have referenced these files already in &lt;code&gt;_Layout.cshtml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For production mode with minified bundles run:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Adding Webpack build to project file
&lt;/h2&gt;

&lt;p&gt;If you don't want run &lt;code&gt;npm build&lt;/code&gt; every time you make a change to your SASS files, you can automate this step by adding the command to your project file.&lt;/p&gt;

&lt;p&gt;Add the following snippet to &lt;code&gt;AspNetBootstrapSass.csproj&lt;/code&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Above setup enables you to customize your Bootstrap distribution to your likings and also enables you to use SASS within your MVC project. You can find the example project here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/robinvanderknaap" rel="noopener noreferrer"&gt;
        robinvanderknaap
      &lt;/a&gt; / &lt;a href="https://github.com/robinvanderknaap/aspnet-bootstrap-sass" rel="noopener noreferrer"&gt;
        aspnet-bootstrap-sass
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Example project showing how to setup Bootstrap SASS in a ASPNET Core project
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;span&gt;Cover photo by &lt;a href="https://unsplash.com/@swimstaralex?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Alexander Sinn&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/code?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>bootstrap</category>
      <category>aspnet</category>
      <category>sass</category>
    </item>
    <item>
      <title>Create EML file from System.Net.Mail.MailMessage</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Sat, 23 Nov 2013 08:00:00 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/create-eml-file-from-system-net-mail-mailmessage-4gef</link>
      <guid>https://dev.to/robinvanderknaap/create-eml-file-from-system-net-mail-mailmessage-4gef</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;For a project I was working on, I needed to create an EML file from the standard MailMessage class which exists in the System.Net.Mail namespace. I came across an &lt;a href="http://www.codeproject.com/Articles/32434/Adding-Save-functionality-to-Microsoft-Net-Mail-Ma" rel="noopener noreferrer"&gt;article&lt;/a&gt; which describes how to extend the save functionality on the MailMessage class. Using the same technique I created an extension method which is able to save the MailMessage class as EML.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this extension method in your solution you can create an EML file like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The extension method is implemented like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h4&gt;
  
  
  Conclusion
&lt;/h4&gt;

&lt;p&gt;EML files are perfect for storing the email message on your file system or in the database. The EML file contains all the data from the email, including attachments. You can open the EML in most mail clients and view the email.&lt;/p&gt;

</description>
      <category>eml</category>
      <category>csharp</category>
      <category>mailmessage</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Bookreview Instant jqGrid by Gabriel Manricks</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Sun, 29 Sep 2013 07:00:00 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/bookreview-instant-jqgrid-by-gabriel-manricks-nl4</link>
      <guid>https://dev.to/robinvanderknaap/bookreview-instant-jqgrid-by-gabriel-manricks-nl4</guid>
      <description>&lt;p&gt;&lt;a href="http://www.trirand.com/jqgridwiki/doku.php" rel="noopener noreferrer"&gt;&lt;em&gt;JqGrid&lt;/em&gt;&lt;/a&gt; &lt;em&gt;is an ajax enabled jQuery plugin for showing and editing data in a grid. I’ve been using jqGrid extensively throughout my projects, and created an&lt;/em&gt; &lt;a href="https://github.com/robinvanderknaap/MvcJqGrid" rel="noopener noreferrer"&gt;&lt;em&gt;Html Helper&lt;/em&gt;&lt;/a&gt; &lt;em&gt;to ease the implementation in ASP.NET MVC. Because of my experience with jqGrid, Packt publishing asked me to review one of their latest books,&lt;/em&gt; &lt;a href="http://bit.ly/1an91tS" rel="noopener noreferrer"&gt;&lt;em&gt;Instant jqGrid by Gabriel Manricks&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The book is mainly focused on people starting with jqGrid, experienced users will probably know most of the stuff that is written in the book and is of no added value to them.&lt;/p&gt;

&lt;p&gt;The book can be divided in two parts. The first part teaches the jqGrid basic concepts, installation and walks you through the steps of creating your first grid. The second and biggest part is called ‘Top 7 features you need to know about’ and takes a deep dive in the most important features of jqGrid.&lt;/p&gt;

&lt;p&gt;Covered in the second part are formatters, adding controls, editing data, connecting to a backend server and a description of some common methods and events of the jqGrid API. I especially liked the the example of connecting jqGrid to the backend. The backend code is written in PHP, but should easily be understood by developers of other platforms. The example shows how to retrieve data from the server, and page, search and edit data on the server, which are the core features of jqGrid. The author describes every step thoroughly and in a clear and simple form.&lt;/p&gt;

&lt;p&gt;What I missed in the book is how to deal with custom styling. It is mentioned you can use jQuery UI themeroler, but doesn’t give any information or example how to further customize the styling of the grid. Also I thought it was too bad the author did not mention the option of virtual scrolling when describing the pager, which I like very much and is easy to implement.&lt;/p&gt;

&lt;p&gt;The book is very easy to read and to follow. After going through the book, and the examples, a developer should have a sound knowledge of jqGrid, and be able to customize the grid to their needs. I remember the pain going through the online documentation and examples when I first started with jqGrid, this book is a real time-saver for people who want to start working with jqGrid. Not every feature or option is covered in the book, it only counts 58 pages, but I think gives you enough baggage to get started and explore more features, like subgrids, treegrids and data grouping, on your own using the online documentation and examples.&lt;/p&gt;

</description>
      <category>jquery</category>
      <category>jqgrid</category>
    </item>
    <item>
      <title>Presenting iDeal.NET</title>
      <dc:creator>Robin van der Knaap</dc:creator>
      <pubDate>Mon, 04 Jun 2012 07:00:00 +0000</pubDate>
      <link>https://dev.to/robinvanderknaap/presenting-ideal-net-41ho</link>
      <guid>https://dev.to/robinvanderknaap/presenting-ideal-net-41ho</guid>
      <description>&lt;p&gt;iDeal is the leading online payment platform in the Netherlands. Last year when working on &lt;a href="http://bidfortickets.nl/" rel="noopener noreferrer"&gt;bidfortickets.nl&lt;/a&gt; I had to create an API to interact with our iDeal provider (Rabobank) to handle all online payments. I noticed that a lot of examples existed out there especially for PHP, but nothing really usable for the .NET ecosystem. So I decided to open source the API on GitHub and call it &lt;a href="https://github.com/robinvanderknaap/iDeal.NET" rel="noopener noreferrer"&gt;iDeal.NET&lt;/a&gt;. The API takes care of all communications with the iDeal provider and is easily integrated into a .NET (web)application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supported iDeal versions
&lt;/h3&gt;

&lt;p&gt;iDeal.NET is aimed at iDeal Professional (Rabobank), iDeal Zelfbouw (ABN Amro), iDeal Integrated and iDeal Advanced (ING Bank). These versions allow for real-time feedback on transactions.&lt;/p&gt;

&lt;p&gt;iDeal.NET does not yet support iDeal Basic (ING Bank), iDeal Hosted , iDeal Lite (Rabobank) and iDeal Zakelijk which are easily implemented in applications but do not allow for real-time feedback on transactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source, samples and documentation
&lt;/h3&gt;

&lt;p&gt;The source code is hosted on GitHub, this is where you also find the documentation. The solution includes a sample ASP.NET MVC application which shows the basis usage of iDeal.NET. The sample application uses &lt;a href="http://www.ideal-simulator.nl/" rel="noopener noreferrer"&gt;www.ideal-simulator.nl&lt;/a&gt; to simulate the entire process of paying and retrieving status of payments. A live version of the example can be found &lt;a href="http://ideal.skaele.it/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Implementation of iDeal is basically about three types of requests: directory requests, transaction requests and status requests. iDeal.NET provides an easy to use API to send the requests and handle the responses.&lt;/p&gt;

&lt;p&gt;A directory request for example, which retrieves a list of bank (issuers), is as easy as:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You can find detailed information about the usage of the API on &lt;a href="https://github.com/robinvanderknaap/iDeal.NET" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>paymentgateway</category>
      <category>dotnet</category>
      <category>ideal</category>
    </item>
  </channel>
</rss>
