Introduction – Linux ASP.Net5 Deployment With Identity

Linux ASP.Net5 Deployment With Identity Authentication follows on from Linux – ASP-DotNet 2 Factor Authentication, which follows on from ASP-DotNet Web App On Linux with Authentication. This article discusses the move from development to production. The starting point for this guide is a working development asp .net5 web application with multi-factor and third-party authentication. The demonstration is based on Ubuntu but should work on all Debian based distributions using SystemD. This guide does not cover testing or other processes usually carried out when moving to production. It only covers preparing the server and project files.

  • Launch Settings – ensure both development and production environments exist
  • Project File – target framework and runtime identifiers
  • Startup.cs – forward headers for reverse proxy (nginx)
  • Program.cs –
  • Enable Email account confirmation.
  • Setting Up Nginx as a reverse proxy server for the web application
  • Adding SSL (HTTPS) with certbot.
  • System environmental settings (not needed if using systemD) vs appsettings.json
  • Adding systemD compatibility to the project files.

Launch Settings Json – Linux ASP.Net5 Deployment With Identity

In the Properties folder is a file “launchsettings.json”. Update this file to add a configuration for production. Optionally you may add another staging section if you have a staging environment.

Original launchSettings.json

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:29338",
      "sslPort": 44308
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebApplication": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
      }
    }
  }
}

Amended launchsettings.json

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:19595",
      "sslPort": 44314
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "LocalHost": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
      }
    },
    "Website": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}

Project File – Linux ASP.Net5 Deployment With Identity

In the project file, both the Target Framework and Runtime Identifier must be set in the Property group.

<PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
        <RuntimeIdentifier>linux-x64</RuntimeIdentifier>        
</PropertyGroup>

Startup.cs

add the following to the very top of the following method

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

Adding systemD compatibility to the project.

Add the nuget package

  • Microsoft.Extensions.Hosting.SystemD

Update Program.cs to use SystemD

.UseSystemd()

inside the following method. If this application is to be on multiple operating systems consider wrapping the statement in an if statement which detects the operating system.

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSystemd()
                .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });

Enable Real Email Account confirmation – Linux ASP.Net5 Deployment With Identity

When deploying to a production environment enabling a real email service is an essential part of the process. It will also need setting up so that the identity system in the web app will use it. Much of the email section is comes from this guide by Mukesh Murugan; however, it is not identical. Changes are to make the system work with identity emails.

Install the Nuget packages

  • MailKit
  • MimeKit

Add section to appsettings.json and environmental settings

"MailSettings": {
  "Mail": "<fromemail>",
  "DisplayName": "<displayname>",
  "Password": "<yourpasswordhere>",
  "Host": "<smtphost>",
  "Port": "<smtp port>"
}

add the corresponding information to the environmental settings as shown below, if this app will be started using systemd remember this needs to go in the systemD startup file, not the systems environmental settings. Before proceeding to add the new classes and interfaces it is suggested to add another folder to keep all the email files in one place.

Add a class to handle the mail settings

the values from the appsettings file will be loaded into this class.

public class MailSettings
    {
        public string Mail { get; set; }
        public string DisplayName { get; set; }
        public string Password { get; set; }
        public string Host { get; set; }
        public int Port { get; set; }
    }

Add a mail request class

this holds the information about the actual email being sent.

using System.Collections.Generic;
using Microsoft.AspNetCore.Http;

namespace Website.Email
{
    public class MailRequest
    {
        public string ToEmail { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }
        public List<IFormFile> Attachments { get; set; }
    }
}

Add an interface for the mail service

Add an interface for a mail service we are about to make, don’t worry about errors in the IDE. They will vanish with the Mail service class, which comes next.

using System.Threading.Tasks;

namespace Website.Email
{
    public interface IMailService
    {
        Task SendEmailAsync(MailRequest mailRequest);
    }
}

Add the Mail Service Class

The mail service class will send the actual email; this is one of the changes from the tutorial it is based on. As well as inheriting from IMailService, this also inherits from. Then I added an extra overload method for public async Task SendEmailAsync(MailRequest mailRequest). This again is to allow it to work with the identity emails built-in.

using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MimeKit;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.AspNetCore.Identity.UI.Services;

namespace Website.Email
{
    public class MailService :IMailService, IEmailSender
    {
        private readonly MailSettings _mailSettings;

        public MailService(IOptions<MailSettings> mailSettings)
        {
            _mailSettings = mailSettings.Value;
        }

        public Task SendEmailAsync(string email, string subject, string htmlMessage)
        {
            MailRequest mailRequest = new MailRequest();
            mailRequest.ToEmail = email;
            mailRequest.Subject = subject;
            mailRequest.Body = htmlMessage;
            mailRequest.Attachments = null;
            return SendEmailAsync(mailRequest);
        }

        public async Task SendEmailAsync(MailRequest mailRequest)
        {
            var email = new MimeMessage();
            email.Sender = MailboxAddress.Parse(_mailSettings.Mail);
            email.To.Add(MailboxAddress.Parse(mailRequest.ToEmail));
            email.Subject = mailRequest.Subject;
            var builder = new BodyBuilder();
            if (mailRequest.Attachments != null)
            {
                byte[] fileBytes;
                foreach (var file in mailRequest.Attachments)
                {
                    if (file.Length > 0)
                    {
                        using (var ms = new MemoryStream())
                        {
                            file.CopyTo(ms);
                            fileBytes = ms.ToArray();
                        }

                        builder.Attachments.Add(file.FileName, fileBytes, ContentType.Parse(file.ContentType));
                    }
                }
            }

            builder.HtmlBody = mailRequest.Body;
            email.Body = builder.ToMessageBody();
            using (var smtp = new SmtpClient())
            {
                smtp.Connect(_mailSettings.Host, _mailSettings.Port, SecureSocketOptions.StartTls);
                smtp.Authenticate(_mailSettings.Mail, _mailSettings.Password);
                await smtp.SendAsync(email);
                smtp.Disconnect(true);
            }
        }
    }
}

Add two lines to Startup.cs to enable dependency injection

Inside the “public void ConfigureServices(IServiceCollection services)” method, two lines. The tells dependency injection to use the MailService class with the interface IMailService. The second tells it to use the same class with the IEmailSender interface. The former will generally be used widely for email, while the latter is used by the asp identity system to send account confirmation and password resets.

services.AddTransient<IMailService, MailService>();
            
services.AddTransient<IEmailSender, MailService>();

Environment Variables

The environment settings are used to replace information stored in user secrets, appsettings.json and appsettings.Development.json during development. However, ubuntu like a number of other distributions is not compatible with the syntax used by the 3rd party login system. The colon(:) must be replaced by a double underscore (__). This only needs setting on the Linux machine. It is converted automatically in the project. So the project files syntax remains unchanged.

System Wide vs User vs SystemD vs File

Environmental settings are typically stored in either a system-wide setting or a user setting. However, the application will likely need to launch at the system start. Which will mean launching it with systemD. When systemD is used, the environmental settings for that application are set in the systemD file. If the application uses another launch mechanism, the Ubuntu system-wide settings file is /etc/environment. Alternatively, other options in the user folders exist for a single user. Microsoft suggests using environmental settings for security. However, system-wide environment settings are available to everyone. When using SystemD the environment variables are set to the user in the systemD.service file. This means only the user-specified will be able to see them.

An alternative is to keep everything in appsettings.json and then make the file so only its own user is able to read it. This is a good idea for the service file too if using the environment settings that way. To make sure only the user who owns the file can read it use

chmod 0600 path/to/file

Original Syntax

Authentication:Facebook:AppId="**idHere**"
Authentication:Facebook:AppSecret="**secretHere**"
Authentication:Google:ClientId="**idHere"
Authentication:Google:ClientSecret="**secretHere**"
Authentication:Microsoft:ClientId="**idHere"
Authentication:Microsoft:ClientSecret="**secretHere**"
Authentication:Twitter:ConsumerAPIKey="**idHere"
Authentication:Twitter:ConsumerSecret="**secretHere**"

Syntax To use on Linux

Authentication__Facebook__AppId="**idHere**"
Authentication__Facebook__AppSecret="**secretHere**"
Authentication__Google__ClientId="**idHere"
Authentication__Google__ClientSecret="**secretHere**"
Authentication__Microsoft__ClientId="**idHere"
Authentication__Microsoft__ClientSecret="**secretHere**"
Authentication__Twitter__ConsumerAPIKey="**idHere"
Authentication__Twitter__ConsumerSecret="**secretHere**"

Nginx – Linux ASP.Net5 Deployment With Identity

Changes to the Nginx configuration files and creating a Nginx server block.

Configuration files

nginx.conf

add the following to /etc/nginx/nginx.conf

#added for asp pages
proxy_buffer_size   128k;
proxy_buffers   4 256k;
proxy_busy_buffers_size   256k;
large_client_header_buffers 4 16k;

Test the Nginx configuration with:

test config (sudo nginx -t)

then restart nginx using:

restart nginx (sudo systemctl restart nginx)

If you have existing server blocks setup do not forget to test these to ensure there are no compatibility issues.

Create / Append proxy.conf

Append the following to /etc/nginx/proxy.conf or create the file if it does not already exist.

proxy_redirect          off;
proxy_set_header        Host $host;
proxy_set_header        X-Real-IP $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header        X-Forwarded-Proto $scheme;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffers           32 4k;

Test the Nginx configuration and restart in the same way as above, then once more test any existing server blocks in use.

Nginx Server Block

sites-available

create a .conf file with a sensible name such as mydomainname.conf in /etc/nginx/sites-available. In the example below areas surround by ** ** are things which need changing to the configuration of the system being setup. Differently to other guides, this forwards to port 5000 rather than 5001, going strait to 5001 (default kestrel HTTPS port) was causing bad gateway errors, whereas using 5000 with kestrels own forwarding avoided the issue.

server {
    server_name **domainNAme**;
    root /var/www/**domainName**;
    index index.html index.htm index.php;

    location / {
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
    proxy_pass         http://localhost:5000;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection keep-alive;
    proxy_set_header   Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Proto $scheme;
    }

    location ~ /\.ht {
        deny all;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
    }

    gzip  on;
    gzip_http_version 1.1;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_types text/plain text/html text/css application/json application/javascript application/x-javascript text/javascript text/xml appli>
    gzip_buffers 16 8k;
    gzip_disable “MSIE [1-6].(?!.*SV1)”;
}

test the config again and assuming its ok restart nginx

Certbot – Linux ASP.Net5 Deployment With Identity

If certbot is not already installed on the server instructions for installing certbot can be found on their site. Once its installed run the following to setup the certificate.

sudo certbot --nginx

When the menu is presented, choose the option which relates to the server block created above. Then in the next option choose to redirect http to https if asked.

SystemD – Linux ASP.Net5 Deployment With Identity

SystemD File

The systemD file must contain the Environment for each of the identity services listed under the environment above. The example below works; however, it is minimal. It is probably advisable to add a restart option if nothing else. SystemD files go in /etc/systemd/system/ and end with a .service. Eg /etc/systemd/system/mywebapp.service.

Items surrounded by double asterisk (** **) need changing to match the system being setup.

[Unit]
Description= **MyWebiste**
After=syslog.target network.target ufw.service mysql.service cron.service
[Service]
Type=notify
User=**myUser**
Group=**myUserGroup**
WorkingDirectory=/var/www/**directory name of the site**
ExecStart=/var/www/**directory name of the site**/**name of the web application**
Environment=USER=**myUser**  HOME=/home/**myUser**  Authentication__Facebook__AppId="**AppIdHere**"  Authentication__Facebook__AppSecret="**App Secret Here**"  

[Install]
WantedBy=multi-user.target

Now enable the service so it starts when the server is started and start the service.

sudo systemctl enable mywebapp.service
sudo systemctl start mywebapp.service

Related Articles – Linux ASP.Net5 Deployment With Identity

Articles related to Linux ASP.Net5 Deployment With Identity

This article has 2 comments

  1. Pingback: Linux - ASP-DotNet 2 Factor Authentication - Sara Payne's BlogSara Payne's Blog

  2. Pingback: ASP-DotNet Web App On Linux with Authentication - Sara Payne's BlogSara Payne's Blog

Leave a Comment

Your email address will not be published. Required fields are marked *