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.
- SSL certificate for dotnet localhost
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 a double asterisk (** **) need changing to match the system being set up. Importantly make sure that the ASPNETCORE_URLS match the same ports found in launchSettings.json
[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=sara Environment=HOME=/home/sara Environment=ASPNETCORE_ENVIRONMENT="**Development**" Environment=ASPNETCORE_URLS="https://localhost:**5003**;http://localhost:**5002**" Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false Environment=Authentication__Facebook__AppId="**app id here**" Environment=Authentication__Facebook__AppSecret="**secret here**" Environment=Authentication__Google__ClientId="**client id here**" Environment=Authentication__Google__ClientSecret="**secret here**" Environment=Authentication__Microsoft__ClientId="**client id here**" Environment=Authentication__Microsoft__ClientSecret="**secret here**" Environment=Authentication__Twitter__ConsumerAPIKey="**consumer key here**" Environment=Authentication__Twitter__ConsumerSecret="**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
dotnet dev-certs https
When using ports other than 5000/5001 it may be necessary to run
dotnet dev-certs https
Or otherwise provide another certificate. This certificate is not the same as the main one which will be used by Nginx / Apache or other similar software.
Related Articles – Linux ASP.Net5 Deployment With Identity
Articles related to Linux ASP.Net5 Deployment With Identity
Hi Sara
I’ve been reading your blog and seen some really good C++ examples.
Would you find any value in making those examples more interactive – allowing readers to modify and execute them directly?
We’ve created a service at https://finchdeploy.com that we hope would make this easier, where you can embed an executable and editable code snippet with a single line of HTML.
We would really value your feedback given your experience as a blogger:
• Is embedding modifiable and executable code examples something you’d find useful?
• If it is, are there any requirements you have from a service to let you include these snippets?
I’d welcome your thoughts.
Kind regards,
Caroline