<?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: bN</title>
    <description>The latest articles on DEV Community by bN (@___bn___).</description>
    <link>https://dev.to/___bn___</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%2F27474%2Fd3269972-d181-41ac-9052-78d88e2f98d3.jpg</url>
      <title>DEV Community: bN</title>
      <link>https://dev.to/___bn___</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/___bn___"/>
    <language>en</language>
    <item>
      <title>Free Customer Identity Access Management with Azure AD B2C</title>
      <dc:creator>bN</dc:creator>
      <pubDate>Fri, 08 Oct 2021 16:43:20 +0000</pubDate>
      <link>https://dev.to/___bn___/free-customer-identity-access-management-with-azure-ad-b2c-34d1</link>
      <guid>https://dev.to/___bn___/free-customer-identity-access-management-with-azure-ad-b2c-34d1</guid>
      <description>&lt;p&gt;&lt;em&gt;Image by &lt;a href="https://pixabay.com/fr/users/thedigitalway-3008341/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=1590455" rel="noopener noreferrer"&gt;TheDigitalWay&lt;/a&gt; from &lt;a href="https://pixabay.com/fr/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=1590455" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article describes how to configure Azure AD B2C to manage identities and access to an application. Thanks to &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/overview" rel="noopener noreferrer"&gt;AAD B2C&lt;/a&gt; you can allow users to sign-up/sign-in to your application and leverage capabilities like SSO, MFA and email verification without a single line of code. You can also modify users flow to add customized steps on self-service customer registration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Customer Identity Access Management solution ?
&lt;/h2&gt;

&lt;p&gt;According to &lt;a href="https://en.wikipedia.org/wiki/Customer_identity_access_management" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;, a CIAM is&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a subset of the larger concept of identity access management (IAM) and is focused specifically on managing the identities of customers who need access to corporate websites, web portals and webshops. Instead of managing user accounts in every instance of a software application of a company, the identity is managed in a CIAM component, making reuse of the identity possible. The biggest differentiator between CIAM and regular (internal) IAM is that in CIAM the consumers of the service manage their own accounts and profile data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Main advantages of using a CIAM are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to create and maintain an custom identity server&lt;/li&gt;
&lt;li&gt;Customers repository reuse accross multiple applications&lt;/li&gt;
&lt;li&gt;Advanced scenarios like MFA, SSO&lt;/li&gt;
&lt;li&gt;Last standard compliance in terms of security&lt;/li&gt;
&lt;li&gt;Compatibility with many identity providers (Azure AD, Facebook, ...)&lt;/li&gt;
&lt;li&gt;User monitoring to detect fraudulent access&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation" rel="noopener noreferrer"&gt;GDPR&lt;/a&gt; compliance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create an Azure Active Directory B2C tenant
&lt;/h2&gt;

&lt;p&gt;The first step is to create a new tenant in which we'll register our application. If you don't have an Azure subscription yet, you can create a new one for free. Simply follow steps from &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;. Following a little summary:&lt;/p&gt;

&lt;p&gt;Don't forget to make sure &lt;em&gt;Microsoft.AzureActiveDirectory&lt;/em&gt; is registered as a resource provider in your subscription before creating the resource.&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%2Fxvxrm2gq6qbjffra71no.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%2Fxvxrm2gq6qbjffra71no.png" alt="Microsoft.AzureActiveDirectory subscription screenshot" width="800" height="157"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then go to the home page, click on "Create a resource", find "Azure Active Directory B2C", click on "Create" and "Create a new Azure AD B2C Tenant". Fill the fields and click on "Create".&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%2F476pxsio7t4620prfl0q.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%2F476pxsio7t4620prfl0q.png" alt="Create Azure Active Directory B2C screenshot" width="652" height="313"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once you clicked on the final "create" button, go grab a ☕ because it will take a while ;-)&lt;/p&gt;

&lt;p&gt;It would appear quite weird but it seems that the tenant creation is synchronous. So after clicking on "Create", do not leave the page until the creation is finished. Otherwise the creation will be cancelled.&lt;/p&gt;
&lt;/blockquote&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%2F88qrg4ztdy0swrtibq43.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%2F88qrg4ztdy0swrtibq43.png" alt="Create tenant event screenshot" width="571" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Register a web application
&lt;/h2&gt;

&lt;p&gt;Next we need to register a web application. The registration process will generate an application ID (also known as "client ID") that'll use to uniquely identify our application. &lt;/p&gt;

&lt;p&gt;Follow &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga#register-a-web-application" rel="noopener noreferrer"&gt;these steps&lt;/a&gt; to register a web application. During the process you will create a client secret. Keep it somewhere because we'll need it later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Client secrets are useful during the development phase or for server-to-server communication when the server storing the secret is in a trusted private place. Please do not store client secrets on the client side (users computer, smartphone, etc.). If you do so, the secret would be exposed and from then should be considered public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Create user flows and custom policies in Azure Active Directory B2C
&lt;/h2&gt;

&lt;p&gt;Now we will create a user flow to enables a user to sign up and sign in with Facebook. To do this, I'm going to follow &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt; from Microsoft. The first thing to do is to choose between a standard user flow and a custom policy. With a standard policy you can select a predefined flow. With The custom policy you can go further and personalize the flow as you wish. Let's choose the second option and follow the instructions.&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%2Fbg6l4ma7su8va084f50c.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%2Fbg6l4ma7su8va084f50c.png" alt="custom policy button screenshot" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the time I'm writing this article, there is a certificate issue on &lt;a href="http://www.contoso.com" rel="noopener noreferrer"&gt;www.contoso.com&lt;/a&gt;. The certificate is registered for *.oneroute.microsoft.com so it is considered invalid. Since Facebook check the certificate validity of the user data deletion URL, it is not possible to use the URL provided in the tutorial.&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%2F92rorp8u2fpo1rsb21o3.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%2F92rorp8u2fpo1rsb21o3.png" alt="invalid url screenshot" width="754" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpmumacnkmv5wkedwmb2.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%2Ftpmumacnkmv5wkedwmb2.png" alt="invalid certificate screenshot" width="449" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn't found any useful information concerning this issue on the web, so I created a new issue on &lt;a href="https://github.com/MicrosoftDocs/azure-docs/issues/81763" rel="noopener noreferrer"&gt;azure-docs repo&lt;/a&gt;. I hope they will transmit the information to the team in charge of maintaining contoso.com&lt;/p&gt;

&lt;p&gt;You can use any valid https URL instead, it doesn't matter for purpose of this tutorial because that URL is only here for legal reasons. For instance you could use &lt;code&gt;https://httpbin.org/&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Tip: setup XML validation in VSCode
&lt;/h3&gt;

&lt;p&gt;At one point of the tutorial, we have to get the custom policy starter pack and modify some XML files in it. In order to prevent dumb errors like typos, we could check the validity of the XML according to the corresponding schema (XSD file). It can be done automatically in VSCode IDE.&lt;/p&gt;

&lt;p&gt;Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml" rel="noopener noreferrer"&gt;XML extension from Red Hat&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open command palette (CTRL+SHIFT+P) and search "Open Settings (JSON)".&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%2F701u3d8tp2eqn2z67xf4.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%2F701u3d8tp2eqn2z67xf4.png" alt="open settings menu screenshot" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;em&gt;settings.json&lt;/em&gt; file opened, add the following sections:&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;"xml.symbols.showReferencedGrammars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"xml.fileAssociations"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TrustFrameworkExtensions.xml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"systemId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TrustFrameworkPolicy_0.3.0.0.xsd"&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;Now if you make a typo, VSCode will tell it to you:&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%2Fbkopda9o87v4awhziekf.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%2Fbkopda9o87v4awhziekf.png" alt="Oops screenshot" width="800" height="73"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Play with policies
&lt;/h2&gt;

&lt;p&gt;Once you finished the tutorial, you should be able to sign-up using an (email, password) or using Facebook. It is now time to play with policies to see what we can get out of it.&lt;/p&gt;

&lt;p&gt;Personaly I'm interested in user migration from a legacy system. After some research over the web I found a very interesting github repository containing dozens of &lt;a href="https://github.com/azure-ad-b2c/samples" rel="noopener noreferrer"&gt;Azure AD B2C samples&lt;/a&gt;. And in the list the one I'm interested in: &lt;a href="https://github.com/azure-ad-b2c/user-migration" rel="noopener noreferrer"&gt;User migration&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Clone the above repository on your computer. To deploy and run this sample we'll need 2 things:&lt;/p&gt;

&lt;p&gt;1) An Azure Blob Storage account.&lt;br&gt;
2) A docker image to host the application on Azure. Because the sample is under aspnetcore 2.0.&lt;/p&gt;

&lt;p&gt;If you don't have an Azure account yet, your can &lt;a href="https://azure.microsoft.com/en-us/free" rel="noopener noreferrer"&gt;create one for free and get 200 USD of Azure credits&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a storage account
&lt;/h3&gt;

&lt;p&gt;Connect to your Azure account and create a new storage account. Go to "Access keys" menu, click on "Show keys" and copy one of the connection strings.&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%2F1u9ctzxa1pxhnrnmprtp.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%2F1u9ctzxa1pxhnrnmprtp.png" alt="Azure storage account access keys" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;em&gt;appsettings.json&lt;/em&gt; and paste the connection string in the appropriate AppSettings:&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;"AppSettings"&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;"BlobStorageConnectionString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;Your connection string to Azure Table that stores your identities to be migrated&amp;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a docker image to publish the sample to Azure
&lt;/h3&gt;

&lt;p&gt;The sample application won't work if you publish it as-is to an Azure Web App because the minimum runtime version is aspnetcore 3.1 and the sample targets 2.0. Hopefully there is Docker to the rescue.&lt;/p&gt;

&lt;p&gt;Open the sample in Visual Studio 2019, right-click on the project name in solution explorer and click on "Add docker support". Visual Studio then scaffold a Dockerfile for your project, but there is a little issue. You should see this error message when building the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the aspnetcore images have been renamed, so the names of the "base" and "build" image are wrong in &lt;em&gt;Dockerfile&lt;/em&gt;. Open &lt;em&gt;Dockerfile&lt;/em&gt; and replace &lt;code&gt;microsoft/aspnetcore:2.0&lt;/code&gt; with &lt;code&gt;mcr.microsoft.com/dotnet/core/aspnet:2.2&lt;/code&gt; and &lt;code&gt;microsoft/aspnetcore-build:2.0&lt;/code&gt; with &lt;code&gt;mcr.microsoft.com/dotnet/core/sdk:2.2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now the image should build normally. If not, check you run the &lt;code&gt;docker build&lt;/code&gt; command in the parent folder of the &lt;em&gt;.csproj&lt;/em&gt; file. For instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/user-migration/jit-migration-v2/source-code &lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"AADB2C.JITUserMigration/Dockerfile"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right-click on the project in solution explorer and select "Publish...". &lt;/p&gt;

&lt;p&gt;Create a new profile targetting Azure App Service Container.&lt;/p&gt;

&lt;p&gt;Create a new webapp and a new registry container.&lt;/p&gt;

&lt;p&gt;Then click "Publish" and after a few minutes your application is online.&lt;/p&gt;

&lt;p&gt;You can now populate the fake database by calling &lt;code&gt;api/test/PopulateMigrationTable&lt;/code&gt; on you freshly published API. The endpoint respond with 200 OK and a JSON body content containing the list of users with passwords.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modify and publish the custom policy
&lt;/h3&gt;

&lt;p&gt;As stated in the sample instructions: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open the policies files, change the tenant name, client_id and IdTokenAudience for Local Account sign-in, and upload the policies to Azure portal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a little omission here: we also need to modify the URL to the REST API in order to use the webapp we just published instead of the default one.&lt;/p&gt;

&lt;p&gt;Open "TrustFrameworkExtensions.xml" and replace &lt;code&gt;http://aadb2cjitusermigrationv2.azurewebsites.net&lt;/code&gt; with the URL of your webapp. You can find the URL on the &lt;em&gt;Overview&lt;/em&gt; page of your &lt;em&gt;App Service&lt;/em&gt; on &lt;a href="//portal.azure.com"&gt;the Azure portal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You also have to remove the Google provider from "TrustFrameworkBase.xml" because we do not have registered a Google App, we only have registered a Facebook App.&lt;/p&gt;

&lt;p&gt;Switch to your AAD B2C account, go to "Identity Experience Framework" and delete the custom policies we created earlier. Then upload the new ones. The order in which you add them matters because some files depends on the others. So add them in &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows?pivots=b2c-custom-policy#upload-the-policies" rel="noopener noreferrer"&gt;this&lt;/a&gt; order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the sign-up/sign-in policy
&lt;/h3&gt;

&lt;p&gt;Select "B2C_1A_JITMIGRAION_SIGNUP_SIGNIN" and click on "Execute now". Enter one of the email address contained in the Azure Table, for instance &lt;code&gt;jeff@contoso.com&lt;/code&gt;. Enter the associated password (1234 by default). Then click on "Sign-in".&lt;/p&gt;

&lt;p&gt;Our REST API is called to validate the user identity and if it is OK the user is migrated to AAD B2C&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%2F0lh0g4nzyg8gxfjgqi3g.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%2F0lh0g4nzyg8gxfjgqi3g.png" alt="migrated user" width="800" height="23"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and removed from Azure Table.&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%2Fnoie2iet85krx1aiod6w.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%2Fnoie2iet85krx1aiod6w.png" alt="no more jeff" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Request for additional information on sign-up screen
&lt;/h2&gt;

&lt;p&gt;It is not possible to request for additional information on the sign-in screen because SSO requires only 2 informations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user unique ID (can be a unique user name, an email or a phone number)&lt;/li&gt;
&lt;li&gt;A password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it is possible to request for additional information during the sign-up process. Imagine you want to add a nickname on the sign-up page. First we have to declare a new claim. Open &lt;em&gt;SocialAndLocalAccounts/TrustFrameworkExtensions.xml&lt;/em&gt; and add the following declaration in &lt;code&gt;BuildingBlocks/ClaimsSchema&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ClaimType&lt;/span&gt; &lt;span class="na"&gt;Id=&lt;/span&gt;&lt;span class="s"&gt;"nickname"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;DisplayName&amp;gt;&lt;/span&gt;Your nickname&lt;span class="nt"&gt;&amp;lt;/DisplayName&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;DataType&amp;gt;&lt;/span&gt;string&lt;span class="nt"&gt;&amp;lt;/DataType&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;UserInputType&amp;gt;&lt;/span&gt;TextBox&lt;span class="nt"&gt;&amp;lt;/UserInputType&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ClaimType&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same file, find the &lt;em&gt;LocalAccountSignUpWithLogonEmail&lt;/em&gt; technical profile and add the following &lt;em&gt;OutputClaim&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"nickname"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to include the nickname in the token's claims, open &lt;em&gt;SocialAndLocalAccounts/SignUpOrSignIn.xml&lt;/em&gt; and add the following output claim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;OutputClaim&lt;/span&gt; &lt;span class="na"&gt;ClaimTypeReferenceId=&lt;/span&gt;&lt;span class="s"&gt;"nickname"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upload the 2 modified policies in IEF and execute the sign-in/sign-up policy. Click on "sign-up" button. You should see a new field "nickname". Fill the fields and create a new account. Once the local account created, you should see the nickname in the returned token claims:&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%2Faia8xjiskbsab9pfj8yg.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%2Faia8xjiskbsab9pfj8yg.png" alt="decoded token" width="640" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Problem: if you re-execute the sign-in/sign-up policy and now sign-in using your new account, the nickname is not present anymore in the token's claims because it has not been persisted. The question is: how can I make the nickname persistent so it would be added automatically on each sign-in?&lt;/p&gt;

&lt;p&gt;You have 2 solutions to handle this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store the nickname on you own during sign-up and &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/add-api-connector-token-enrichment?pivots=b2c-custom-policy" rel="noopener noreferrer"&gt;enrich tokens with claims from external sources using API connectors&lt;/a&gt; during sign-in.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-custom-attributes?pivots=b2c-custom-policy" rel="noopener noreferrer"&gt;Define custom attributes in Azure Active Directory B2C&lt;/a&gt; and let AAD B2C store the custom attribute for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just follow the steps described in one of the above article, sign-up a new user, then sign-in and now you should see the nickname in the claims.&lt;/p&gt;

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

&lt;p&gt;AAD B2C is a comprehensive CIAM solution and can allow you to save a lot of time by avoiding you to implement your own identity and SSO provider. Thanks to this, you can focus on your business and deliver more functionalities to your customers while still offering a premium authentication experience.&lt;/p&gt;

&lt;p&gt;I am discovering AAD B2C and wrote this article at the same time I was learning about it. I found they were some omission or imprecision in the official documentation. Moreover, the documentation is very intimidating because it is huge. So I thought it could be useful for someone to have a summary of a few AAD B2C features with tips to avoid pitfalls. I barely scratched the surface of what it has to offer and have yet many things to learn. Maybe It'll be worth it to write a second article on this subject when I'll be more experimented on it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Serve a Single Page Application along with its backend API thanks to NGINX reverse proxy</title>
      <dc:creator>bN</dc:creator>
      <pubDate>Sat, 25 Sep 2021 16:42:17 +0000</pubDate>
      <link>https://dev.to/___bn___/serve-a-single-page-application-along-with-its-backend-api-thanks-to-nginx-reverse-proxy-2h5c</link>
      <guid>https://dev.to/___bn___/serve-a-single-page-application-along-with-its-backend-api-thanks-to-nginx-reverse-proxy-2h5c</guid>
      <description>&lt;p&gt;&lt;em&gt;Thanks to &lt;a href="https://pixabay.com/fr/users/kreatikar-8562930/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=3406770" rel="noopener noreferrer"&gt;Mudassar Iqbal&lt;/a&gt; de &lt;a href="https://pixabay.com/fr/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=3406770" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt; for the illustration image&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this article, I describe how you can build and host a full website on your own server. The website is composed of an Angular SPA for the front part and an ASP.NET web API for the backend. Everything is described from A to Z and you can find a full working example on &lt;a href="https://github.com/bNobo/FullWebsiteDemo" rel="noopener noreferrer"&gt;my github repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you already know how to setup a SPA with a backend API, feel free to skip the first sections and go directly to Deploy to production.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://angular.io/guide/setup-local" rel="noopener noreferrer"&gt;Angular CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnet.microsoft.com/download/dotnet/5.0" rel="noopener noreferrer"&gt;ASP.NET (5+ or core) SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A machine with Linux installed (WSL should be OK but not tested)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/" rel="noopener noreferrer"&gt;NGINX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/Download" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use whatever version of Angular and .NET core you prefer. You could also scaffold a React web site with a GO backend if you wish. The website and API we'll build are just here for the sake of the demonstration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web API
&lt;/h2&gt;

&lt;p&gt;First we need a backend. Let's create a new ASP.NET web api.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;webapi
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;webapi
&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet new webapi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's build and run it.&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="nv"&gt;$ &lt;/span&gt;dotnet build
&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a web browser and navigate to &lt;em&gt;&lt;a href="https://localhost:5001/weatherforecast" rel="noopener noreferrer"&gt;https://localhost:5001/weatherforecast&lt;/a&gt;&lt;/em&gt;. You should see a JSON result similar to:&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="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2021-09-26T10:15:16.6511246+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;-7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Mild"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2021-09-27T10:15:16.6512592+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Freezing"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2021-09-28T10:15:16.6512606+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Scorching"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2021-09-29T10:15:16.6512609+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;-17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Hot"&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2021-09-30T10:15:16.6512612+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureC"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;-14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"temperatureF"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Bracing"&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;Dotnet automatically created a controller which returns fake data for us, perfect!&lt;/p&gt;

&lt;p&gt;We need a little modification here: for the reverse proxy to be able to distinguish requests intended for the back from requests intended for the front, let's add an "api" segment in the route. Open the project with VSCode:&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="nv"&gt;$ &lt;/span&gt;code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find &lt;em&gt;WeatherForecastController.cs&lt;/em&gt; file and change the route 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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the route to weather forecast is &lt;em&gt;&lt;a href="https://localhost:5001/api/weatherforecast" rel="noopener noreferrer"&gt;https://localhost:5001/api/weatherforecast&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We need one last modification: in the final version of the website, front and back will be served from the same URL thanks to the reverse proxy. But during the development phase, the backend will be served on port 5001 and the front on port 4200. This will endup with a &lt;a href="https://developer.mozilla.org/fr/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;CORS&lt;/a&gt; error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access to XMLHttpRequest at 'https://localhost:5001/api/weatherforecast' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, in order to be able to debug our website locally we need to add CORS headers on the backend side. Hopefully, .NET has a middleware which permits to do it easily. Open the &lt;code&gt;Startup&lt;/code&gt; class and add the following at the beginning of &lt;code&gt;ConfigureServices&lt;/code&gt; method:&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="cp"&gt;#if DEBUG
&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;AddCors&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;AddPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"corsPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;builder&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;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyHeader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyMethod&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllowAnyOrigin&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="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above registers the CORS middleware and create a policy called "corsPolicy". Now it is registered we can add it to the pipeline. Open the &lt;code&gt;Configure&lt;/code&gt; method and add:&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;UseCors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"corsPolicy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Allowing anything could be dangerous, so don't use this configuration in production. You should only allow trusted origins in production. Note the &lt;code&gt;#if&lt;/code&gt; pragma which I use to add CORS middleware only in DEBUG configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it for the API, now let's build the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Website
&lt;/h2&gt;

&lt;p&gt;Let's quickly scaffold a new SPA thank to Angular CLI.&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="nv"&gt;$ &lt;/span&gt;ng new website
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we tell Angular to create a new application called "website". &lt;/p&gt;

&lt;p&gt;Follow the instructions and wait for Angular to scaffold the website for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? Do you want to enforce stricter type checking and stricter bundle budgets in the workspace?
  This setting helps improve maintainability and catch bugs ahead of time.
  For more information, see https://angular.io/strict Yes
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS   [ https://sass-lang.com/documentation/syntax#scss
 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once finished, build the website and launch it to ensure it works.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;website
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;ng build
&lt;span class="nv"&gt;$ &lt;/span&gt;ng serve &lt;span class="nt"&gt;-o&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a page similar to this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn0nffb40bd3ee31u4j0.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%2Fcn0nffb40bd3ee31u4j0.png" alt="angular screenshot" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we want our website to call the web API and show the weather forecast. First we need a new constant to store the backend URL. Open &lt;em&gt;environment.ts&lt;/em&gt; and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://localhost:5001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also add &lt;em&gt;baseUrl&lt;/em&gt; to &lt;em&gt;environment.prod.ts&lt;/em&gt; but keep it empty for now.&lt;/p&gt;

&lt;p&gt;We will need the &lt;em&gt;HttpClientModule&lt;/em&gt; to call the backend so let's add it. Open &lt;em&gt;app.module.ts&lt;/em&gt; and add the import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpClientModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to register it in the &lt;em&gt;imports&lt;/em&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;BrowserModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;HttpClientModule&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;em&gt;app.component.ts&lt;/em&gt; and modify the content like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/environments/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;templateUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;styleUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.component.scss&lt;/span&gt;&lt;span class="dl"&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;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppComponent&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnInit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;forecasts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HttpClient&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;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/weatherforecast&lt;/span&gt;&lt;span class="dl"&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;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forecasts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;Finally, open &lt;em&gt;app.component.html&lt;/em&gt;, drop all its content and replace it with:&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;div&lt;/span&gt; &lt;span class="na"&gt;*ngFor=&lt;/span&gt;&lt;span class="s"&gt;"let forecast of forecasts"&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;{{ forecast.date | date }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt; 
 &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{{ forecast.summary }}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;{{ forecast.temperatureC }} celcius degrees&lt;span class="nt"&gt;&amp;lt;/h3&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;Yeah, that's ugly I know, but making a beautiful front is not the purpose of this article. It is just for the sake of the demonstration :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to production &lt;a id="deploy"&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we have a "beautiful" website, we're so proud of it that we want to put it on production. So people everywhere in the world could view it. And because we can publish and host it on our own, why wouldn't we do it? So let's start.&lt;/p&gt;

&lt;p&gt;Firstly, we'll need a machine with Linux installed on it. If you don't have a machine with Linux on it and don't have time to install one, don't worry: you can install a linux distro directly on windows and leverage &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/about" rel="noopener noreferrer"&gt;Windows Subsystem for Linux&lt;/a&gt;. I didn't tested it but that theoretically should work as well. Personally I'm going to host the website on my Raspberry Pi 3 (Debian Stretch).&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a domain name
&lt;/h3&gt;

&lt;p&gt;In order to make your website easily accessible, you'll need a domain name. It will be very more convenient than typing the public IP address of you server. Moreover, there will be a single static entry point for your website. If your IP address change you'll just have to update it on the DNS et voilà. But it is preferable to request a static IP if you can.&lt;/p&gt;

&lt;p&gt;There are plenty of domain name providers, some paid, other free. Choose the one you prefer. Personally I'm going to use &lt;a href="https://www.duckdns.org/" rel="noopener noreferrer"&gt;Duck DNS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The setup is pretty easy. First you need to get your public IP. You can grab it very easily, just type &lt;a href="https://www.bing.com/search?q=what+is+my+ip" rel="noopener noreferrer"&gt;"what is my ip" on Microsoft Bing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq826wvarg2b1vcoi68z.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%2Faq826wvarg2b1vcoi68z.png" alt="bing screenshot" width="414" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note your public IP and go to &lt;a href="https://www.duckdns.org/" rel="noopener noreferrer"&gt;Duck DNS&lt;/a&gt;. You need to create a free account if you don't have one yet. Choose a domain name, type it and click "add domain".&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%2Fqg30l6s0bwibz8gcdbcs.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%2Fqg30l6s0bwibz8gcdbcs.png" alt="duck DNS screenshot 1" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then enter your IP address and click "update ip".&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%2Fdcvmybixo5ys8nyj1pht.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%2Fdcvmybixo5ys8nyj1pht.png" alt="duck DNS screenshot 2" width="800" height="64"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bind you public IP to the local IP of your server
&lt;/h3&gt;

&lt;p&gt;Now every request sent to &lt;code&gt;&amp;lt;your_domain&amp;gt;.duckdns.org&lt;/code&gt; will be routed to your public IP address, but there is still a missing piece : you didn't tell your router where to redirect the TCP stream. So the packets are lost because your router do not know where to reroute them.&lt;/p&gt;

&lt;p&gt;This part is maybe the trickier one because the setup depends on your internet provider and local network configuration tool supplied by him. So I can't tell you the detailed steps for your specific case. You have to find where to create port redirection rules, also known as &lt;a href="https://fr.wikiversity.org/wiki/NAT_%26_PAT" rel="noopener noreferrer"&gt;"NAT &amp;amp; PAT"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you found it, create two rules:&lt;/p&gt;

&lt;p&gt;1) One to redirect port 443 from public IP to port 443 on the local server IP (the one that'll host the website, raspberry PI in my case)&lt;br&gt;
2) Another one to redirect port 80 from public IP to port 80 on the same local server. This second one will be useful to automatically setup SSL using &lt;em&gt;Certbot&lt;/em&gt;. &lt;a id="port80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ut5gd442x7knfdkhygd.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%2F7ut5gd442x7knfdkhygd.png" alt="NAT &amp;amp; PAT screenshot" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Build and publish the web api
&lt;/h3&gt;

&lt;p&gt;Return to &lt;em&gt;webapi&lt;/em&gt; folder and type:&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="nv"&gt;$ &lt;/span&gt;dotnet publish &lt;span class="nt"&gt;--configuration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;/bin/Release/net5.0/publish/&lt;/code&gt; and copy every files from there to the destination server. You can use &lt;em&gt;ssh&lt;/em&gt; and &lt;em&gt;scp&lt;/em&gt; for example:&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="nv"&gt;$ &lt;/span&gt;ssh pi@192.168.1.25 &lt;span class="nb"&gt;mkdir&lt;/span&gt; /home/pi/fullwebsitedemo-api
&lt;span class="nv"&gt;$ &lt;/span&gt;scp &lt;span class="nt"&gt;-r&lt;/span&gt; ./bin/Release/net5.0/publish/&lt;span class="k"&gt;*&lt;/span&gt; pi@192.168.1.25:/home/pi/fullwebsitedemo-api/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microsoft.OpenApi.dll                                                                 100%  170KB   4.2MB/s   00:00
Swashbuckle.AspNetCore.Swagger.dll                                                    100%   16KB 937.5KB/s   00:00
Swashbuckle.AspNetCore.SwaggerGen.dll                                                 100%   79KB   1.6MB/s   00:00
Swashbuckle.AspNetCore.SwaggerUI.dll                                                  100% 3231KB   5.1MB/s   00:00
appsettings.Development.json                                                          100%  162    10.0KB/s   00:00
appsettings.json                                                                      100%  192    12.0KB/s   00:00
web.config                                                                            100%  492    23.7KB/s   00:00
webapi.deps.json                                                                      100%  109KB   2.0MB/s   00:00
webapi.dll                                                                            100%   11KB 609.2KB/s   00:00
webapi.exe                                                                            100%  123KB   2.2MB/s   00:00
webapi.pdb                                                                            100%   20KB   1.1MB/s   00:00
webapi.runtimeconfig.json                                                             100%  304    29.6KB/s   00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can create an RSA key pair and use &lt;em&gt;-i&lt;/em&gt; flag instead of typing your password, but it is beyond the scope of this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that binaries are on the server, we can serve them using Kestrel. The first step is to install .NET5 runtime because it is not installed by default on Debian Stretch. It is pretty straightforward, simply follow the instructions &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/install/linux-debian#debian-9-" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You don't need to install the SDK. Unless you plan to compile on the server, the runtime is sufficient.&lt;/p&gt;

&lt;p&gt;Next we'll use &lt;em&gt;systemd&lt;/em&gt; to create and manage the service, so it will be automatically run at startup and restarted in case of failure. &lt;/p&gt;

&lt;p&gt;Create a file named &lt;em&gt;fullwebsitedemo-api.service&lt;/em&gt; in your home folder and put this inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Full website demo API&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/pi/fullwebsitedemo-api&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/pi/dotnet/dotnet /home/pi/fullwebsitedemo-api/webapi.dll&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;
&lt;span class="py"&gt;SyslogIdentifier&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;fullwebsitedemo-api&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;pi&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ASPNETCORE_ENVIRONMENT=Production&lt;/span&gt;
&lt;span class="py"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ASPNETCORE_URLS=http://0.0.0.0:5002&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I'm using port 5002 because I already have another application listening on port 5000 on my Raspberry. You can use any port as soon as it is free and not reserved. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Install and start the service with these commands:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; /home/pi/fullwebsitedemo-api.service
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start fullwebsitedemo-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make sure the web api is responding correctly you can test it from the web browser:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://&amp;lt;local_server_ip&amp;gt;:5002/api/weatherforecast&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%2Fzefntfnnl5nst9ttbjny.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%2Fzefntfnnl5nst9ttbjny.png" alt="test web api screenshot" width="649" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build and publish the frontend
&lt;/h3&gt;

&lt;p&gt;Remember the &lt;em&gt;baseUrl&lt;/em&gt; constant in our &lt;em&gt;environment.prod.ts&lt;/em&gt; file? We left it empty because we didn't had a domain name yet. Now we have to set it before publication. Open the file and fill &lt;em&gt;baseUrl&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://&amp;lt;your_domain&amp;gt;.duckdns.org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go to &lt;em&gt;website&lt;/em&gt; directory and build the application:&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="nv"&gt;$ &lt;/span&gt;ng build &lt;span class="nt"&gt;--prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And copy files from &lt;em&gt;dist&lt;/em&gt; folder to &lt;em&gt;fullwebsitedemo&lt;/em&gt; folder 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="nv"&gt;$ &lt;/span&gt;ssh pi@192.168.1.25 &lt;span class="nb"&gt;mkdir&lt;/span&gt; /home/pi/fullwebsitedemo
&lt;span class="nv"&gt;$ &lt;/span&gt;scp &lt;span class="nt"&gt;-r&lt;/span&gt; ./dist/&lt;span class="k"&gt;*&lt;/span&gt; pi@192.168.1.25:/home/pi/fullwebsitedemo/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup NGINX
&lt;/h3&gt;

&lt;p&gt;We are now ready to setup NGINX to serve our website. NGINX will have a few responsibilities:&lt;/p&gt;

&lt;p&gt;1) It will serve static files of the frontend and redirect automatically to &lt;em&gt;index.html&lt;/em&gt; when a page is not found, ensuring the SPA will work well and refresh correctly (F5).&lt;br&gt;
2) It will act as a reverse proxy allowing us to have only one domain name and a single SSL certificate for both frontend and backend. And moreover: no more CORS issues.&lt;br&gt;
3) It will load the SSL certificate allowing website to work properly over HTTPS.&lt;br&gt;
4) It will enforce SSL by redirecting HTTP to HTTPS automatically.&lt;/p&gt;

&lt;p&gt;First you need to &lt;a href="https://nginx.org/en/linux_packages.html#Debian" rel="noopener noreferrer"&gt;install NGINX&lt;/a&gt;. Follow the instructions depending on your Linux distro.&lt;/p&gt;

&lt;p&gt;Once installed, open &lt;em&gt;nginx.conf&lt;/em&gt; file:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;em&gt;http&lt;/em&gt; section, add a first &lt;em&gt;server&lt;/em&gt; section that'll handle SSL redirection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# ssl redirection
&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="n"&gt;default_server&lt;/span&gt;;
    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;fullwebsitedemo&lt;/span&gt;.&lt;span class="n"&gt;duckdns&lt;/span&gt;.&lt;span class="n"&gt;org&lt;/span&gt;;
    &lt;span class="n"&gt;return&lt;/span&gt; &lt;span class="m"&gt;301&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;://$&lt;span class="n"&gt;host&lt;/span&gt;$&lt;span class="n"&gt;request_uri&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a second &lt;em&gt;server&lt;/em&gt; section that'll configure the reverse proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; {              
    &lt;span class="n"&gt;server_name&lt;/span&gt; &lt;span class="n"&gt;fullwebsitedemo&lt;/span&gt;.&lt;span class="n"&gt;duckdns&lt;/span&gt;.&lt;span class="n"&gt;org&lt;/span&gt;;

    &lt;span class="c"&gt;# reroute "api" segment to asp.net webapi hosted by Kestrel
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; /&lt;span class="n"&gt;api&lt;/span&gt; {
        &lt;span class="n"&gt;proxy_pass&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="n"&gt;localhost&lt;/span&gt;:&lt;span class="m"&gt;5002&lt;/span&gt;;
        &lt;span class="n"&gt;proxy_set_header&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt; $&lt;span class="n"&gt;host&lt;/span&gt;;
    }

    &lt;span class="c"&gt;# serve static files of the SPA
&lt;/span&gt;    &lt;span class="n"&gt;location&lt;/span&gt; / {
        &lt;span class="n"&gt;root&lt;/span&gt; /&lt;span class="n"&gt;home&lt;/span&gt;/&lt;span class="n"&gt;pi&lt;/span&gt;/&lt;span class="n"&gt;fullwebsitedemo&lt;/span&gt;/&lt;span class="n"&gt;website&lt;/span&gt;;
        &lt;span class="n"&gt;try_files&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt; $&lt;span class="n"&gt;uri&lt;/span&gt;/ /&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;
        &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close &lt;em&gt;nginx.conf&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Remove the default nginx website otherwise it will conflict with the new &lt;em&gt;default_server&lt;/em&gt; you've just defined.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/nginx/sites-enabled/default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Don't be afraid of hardly removing it. There is a copy under &lt;code&gt;/etc/nginx/sites-available&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Setup SSL
&lt;/h3&gt;

&lt;p&gt;The last step is to install an SSL certificate to allow secure (encrypted) communication over HTTPS. It is absolutely mandatory if you want your website to provide some advanced features such as push notifications. Hopefully we can obtain a free SSL certificate thanks to &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's encrypt&lt;/a&gt; and set it up automatically thanks to &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First &lt;a href="https://certbot.eff.org/lets-encrypt/debianstretch-nginx" rel="noopener noreferrer"&gt;install Certbot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Certbot&lt;/em&gt; will temporarily rewrite &lt;em&gt;nginx.conf&lt;/em&gt; to serve a file containing random data (acme challenge) in order to prove you are in control of the domain name for which you want to obtain a certificate. This file will be served statically on http port 80, that's why we had to create a NAT rule above in this article.&lt;/p&gt;

&lt;p&gt;When there is no more doubt on the fact you are the owner of the domain, an SSL certificate is issued for your domain name and installed on the server. &lt;em&gt;nginx.conf&lt;/em&gt; is automatically modified to load the certificate associated with your website.&lt;/p&gt;

&lt;p&gt;Finally 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;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after a few seconds your website is up with SSL.&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%2F63sucgogqcjycwzk8aan.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%2F63sucgogqcjycwzk8aan.png" alt="final website screenshot" width="489" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic certificate renewal
&lt;/h3&gt;

&lt;p&gt;Let's encrypt certificate is free but it is valid for only 3 months. So you have to renew it quite regularly if you don't want to encounter issues with your website.&lt;/p&gt;

&lt;p&gt;Manually renew each 3 months can be boring and there is a risk of human error with every manual operation. &lt;/p&gt;

&lt;p&gt;So there is an ultimate step if you don't want to bother with renewal: automate it. The simpler way to do it is by creating a CRON task.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following line at the end of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 3 * * * certbot renew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the line above, the command &lt;code&gt;certbot renew&lt;/code&gt; will be executed every day at 03:03 AM. If the certificate is about to expire, then a new one will be issued and installed. If not, the command will just do nothing.&lt;/p&gt;

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

&lt;p&gt;If you've made it this far, you should have a better understanding of NGINX, SSL and Certbot. If you have questions, see some mistakes or have any remarks or suggestions, please comment bellow. I hope you've found this article useful and I thank you for reading it.&lt;/p&gt;

&lt;p&gt;You can find the full working example at &lt;a href="https://github.com/bNobo/FullWebsiteDemo" rel="noopener noreferrer"&gt;https://github.com/bNobo/FullWebsiteDemo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>ssl</category>
      <category>certbot</category>
      <category>dns</category>
    </item>
    <item>
      <title>Free certified SSL certificate in ASP.NET 5 Kestrel application</title>
      <dc:creator>bN</dc:creator>
      <pubDate>Sat, 10 Apr 2021 19:23:04 +0000</pubDate>
      <link>https://dev.to/___bn___/free-certified-ssl-certificate-in-asp-net-5-kestrel-application-kgn</link>
      <guid>https://dev.to/___bn___/free-certified-ssl-certificate-in-asp-net-5-kestrel-application-kgn</guid>
      <description>&lt;p&gt;&lt;em&gt;Image par &lt;a href="https://pixabay.com/fr/users/skylarvision-2957633/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=3344700" rel="noopener noreferrer"&gt;skylarvision&lt;/a&gt; de &lt;a href="https://pixabay.com/fr/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=3344700" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As part of a little side project I'm working on, I want to host an ASP.NET Web API on my own server at home. The purpose of this API is to send push notifications to an Angular client application. It is the main reason why I had to set up SSL communication for my web site: service worker needs the communication to be secured by TLS with a valid certificate in order to allow push notications. But that's not the subject of this article. Here I will focus on creating the certificate and loading it into Kestrel.&lt;/p&gt;

&lt;p&gt;For the development phase I used a self-signed certificate, but now that I want to deploy it I need a real certificate certified by a certification authority. Because it is not a commercial but personal project I want to keep it entirely free. So I decided to generate a certificate with Let's Encrypt. Until recently, ASP.Net core was only supporting .PFX files and not .PEM files provided by Let's Encrypt. But .NET 5 has &lt;a href="https://github.com/dotnet/aspnetcore/issues/4706" rel="noopener noreferrer"&gt;rectified the situation in version 5.0.0-preview8&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I could use a reverse-proxy like nginx or haproxy and let it handle the SSL stuff, but I wanted to keep it as simple as possible, so I decided to directly serve it using Kestrel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;A domain name that you own. Personally I registered to &lt;a href="//www.duckdns.org"&gt;Duck DNS&lt;/a&gt; to get a free one.&lt;/li&gt;
&lt;li&gt;An ASP.NET web site binded to your domain (to prove you are the owner of the domain)&lt;/li&gt;
&lt;li&gt;A bash, preferably Linux ;) but Windows is OK too&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Most of Internet providers allow you to configure NAT/PAT and DynDNS, so it should not be a problem to bind your DNS to an open port on a machine in your local network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Obtain a free certificate
&lt;/h1&gt;

&lt;p&gt;Most of certification authorities sell their certificates and they are expensive. This is because the process of registering often inquires verification made by a human. Hopefully there is &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let's Encrypt&lt;/a&gt; to the rescue. Let's Encrypt is also a certificate authority but it is nonprofit. It aims to &lt;a href="https://letsencrypt.org/docs/faq/#what-does-it-cost-to-use-let-s-encrypt-is-it-really-free" rel="noopener noreferrer"&gt;create a more secure and privacy-respecting Web&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first thing you have to do is to install &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt;. There are many ways to do so depending on your system so I won't go into details. The recommanded way is to use snap. Personnaly I simply made a&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it did the job.&lt;/p&gt;

&lt;p&gt;Now that you have certbot installed you can generate a new certificate and a private key. Once again there are many ways to do this depending on your server and certbot can be configured to automatically renew your certificate before it expires. Personnaly I used the manual way and will configure autorenew later:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certonly &lt;span class="nt"&gt;--manual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Certbot first ask you what is your domain name and then request you to prove you are in control of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Create a file containing just this data:

eZ4dZ4xUrIn7Fkyuy0Vd9giY5FoEPnm5cfGQXs_EDy0.gj7wCMjUoSpRopo7wvqqw5spi23jZdjLjtlCa00Cf2c

And make it available on your web server at this URL:

http://ssldemo.dev.to/.well-known/acme-challenge/eZ4dZ4xUrIn7Fkyuy0Vd9giY5FoEPnm5cfGQXs_EDy0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify your website to make the file available at the requested URL and then press ENTER. Certbot will download the file and verify that the content matches with what was requested. It will then issue a new certificate associated with your domain name and the private key used to sign the certicate. Files are created under&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/letsencrypt/live/ssldemo.dev.to/fullchain.pem
/etc/letsencrypt/live/ssldemo.dev.to/privkey.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy these two files in a secure place on the machine hosting your website and make sure no one can access them except your web application.&lt;/p&gt;

&lt;h1&gt;
  
  
  Kestrel configuration
&lt;/h1&gt;

&lt;p&gt;Make sure your website application target at least .NET 5.0. You should have &lt;code&gt;&amp;lt;TargetFramework&amp;gt;net5.0&amp;lt;/TargetFramework&amp;gt;&lt;/code&gt; in your .csproj file.&lt;/p&gt;

&lt;p&gt;You now have to instruct Kestrel how to load your certificate. Open appsetting.json file and add a Kestrel 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="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"Kestrel"&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;"Certificates"&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;"Default"&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;"Path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path/to/fullchain.pem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"KeyPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path/to/privkey.pem"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You could also use environment variables if you prefer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's it ! The method &lt;code&gt;ConfigureWebHostDefaults&lt;/code&gt; uses Kestrel as the web server so it will use the Kestrel section to configure itself. Just make sure you load appsettings.json at startup (it is the case by default when you use &lt;code&gt;CreateDefaultBuilder&lt;/code&gt;). Thus, the provided SSL certificate and the private key will be loaded and used by your server. If you don't use &lt;code&gt;ConfigureWebHostDefaults&lt;/code&gt; then ensure that you have &lt;code&gt;UseKestrel&lt;/code&gt; on your webBuilder and it should be OK.&lt;/p&gt;

&lt;p&gt;Now when you request your web API via HTTPS you should see a valid certificate in the address bar of your web browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpcnj02farj9s1xwso369.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%2Fpcnj02farj9s1xwso369.png" alt="address bar with ssl" width="433" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I wrote this article because I didn't find many documentation on how to configure Kestrel to load PEM SSL certificate. It is my first article and I hope you will find it interesting, maybe even usefull.&lt;/p&gt;

&lt;p&gt;If you are interested in, you can find the source code of my current side-project for which I had to load PEM SSL certificate in Kestrel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/bNobo/wind-forecast-api" rel="noopener noreferrer"&gt;https://github.com/bNobo/wind-forecast-api&lt;/a&gt; : the web API&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bNobo/wind-forecast-website" rel="noopener noreferrer"&gt;https://github.com/bNobo/wind-forecast-website&lt;/a&gt; : the Angular client&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>net5</category>
      <category>kestrel</category>
    </item>
  </channel>
</rss>
