Asp.Net Core 2.1.0, Individual Authentication issues after migrating from 2.0.x

Asp.Net Core 2.1.0 is here!

I’ve decided to get stuck in straight away with an application I have in active development as I would have needed to implement functionality similar to the GDPR template support anyway, which looks great.

Migration from 2.0 went almost without a hitch. I had a small issue, however, where I couldn’t seem to login. I stepped through the request/middleware/response with a debugger but couldn’t see anything glaringly obvious, until it dawned on me that this is a side-effect of the new GDPR function.

Cookies will no longer be added client-side until the user has consented to them, or unless they’re created with IsEssential set to true. My authentication uses cookies..

With a small change in my startup.cs ConfigureServices method I was all set again.

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/Users/Login";
    options.LogoutPath = "/Security/Logout";
    options.AccessDeniedPath = "/Security/AccessDenied";
    options.SlidingExpiration = true;
    options.Cookie = new CookieBuilder
    {
        HttpOnly = true,
        Name = ".MyApp.Security.Cookie",
        Path = "/",
        SameSite = SameSiteMode.Lax,
        SecurePolicy = CookieSecurePolicy.SameAsRequest,
        IsEssential = true
    };
});

Going forward this cookie will always be stored regardless of the user’s consent. If you’d like the user to always consent even to this cookie, you will need to implement the templates described in the article first, otherwise you’ll be unable to login – even during development.

Asp.Net Core 2.0 C# Sending Errors by Email using ILogger

Asp.Net Core’s new ILogger interface has been met with mixed reviews. Personally I’m quite a fan as it involves minimal boilerplate to get some decent logging to Azure and the Windows Event Log amongst other targets. One thing that’s not supported out of the box is sending production errors by email.

Implementing ILoggerProvider

Firstly we need our own Logger Provider which we’ll name EmailLoggerProvider. For dependency injection purposes this will implement our own IEmailLoggerProvider class which in turn inherits from the native ILoggerProvider.

The CreateLogger method will return an IEmailLogger which we’ll implement later on.

public interface IEmailLoggerProvider : ILoggerProvider
{
}
public class EmailLoggerProvider : IEmailLoggerProvider
{
    private readonly IEmailLogger _emailLogger;

    public EmailLoggerProvider(IEmailLogger emailLogger)
    {
        _emailLogger = emailLogger;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return _emailLogger;
    }

    public void Dispose()
    {
    }
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

In order for asp.net to know about this custom logger the provider needs to be addded to the Logger Factory, we also need to register the services for dependency injecion. Both of these tasks are dealt with in startup.cs.

Add the following to the ConfigureServices method:

services.AddSingleton<IEmailLogger, EmailLogger>();
services.AddSingleton<IEmailLoggerProvider, EmailLoggerProvider>();

The Configure method needs to accept an ILoggerFactory and an IEmailLoggerProvider.

public void Configure(IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory,
    IEmailLoggerProvider emailLoggerProvider)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        loggerFactory.AddProvider(emailLoggerProvider);
    }
    
    app.UseMvcWithDefaultRoute();
}

Implementing ILogger

Now the real work begins, our EmailLogger will now be called whenever there’s an error in production, we need to create this class as a custom implementation of ILogger. To do this I created an IEmailLogger interface which inherits from ILogger.

public interface IEmailLogger : ILogger
{
}

Now for the rather large EmailLogger.cs, I have kept this in one file for the purposes of this blog but I would recommend refactoring this into smaller classes.

 public class EmailLogger : IEmailLogger
    {
        private readonly IOptions<SmtpDetails> _smtpDetails;
        private readonly IConfiguration _configuration;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IEmailSender _emailSender;
        private readonly UserManager<ApplicationUser> _userManager;

        public EmailLogger(
            IOptions<SmtpDetails> smtpDetails,
            IConfiguration configuration,
            IHttpContextAccessor httpContextAccessor,
            IEmailSender emailSender,
            UserManager<ApplicationUser> userManager)
        {
            _smtpDetails = smtpDetails;
            _configuration = configuration;
            _httpContextAccessor = httpContextAccessor;
            _emailSender = emailSender;
            _userManager = userManager;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {

            if (exception != null)
            {
                // Add the details of the Http Request if present
                var stringBuilder = new StringBuilder();
                var httpContext = _httpContextAccessor.HttpContext;
                if (httpContext != null)
                {
                    var request = httpContext.Request;
                    stringBuilder.Append("User: ").Append(_userManager.GetUserName(httpContext.User)).Append("<br/>")
                        .Append("Address: ").Append($"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}").Append("<br/>")
                        .Append("Local IP address: ").Append(httpContext.Connection.LocalIpAddress).Append("<br/>")
                        .Append("Remote IP address: ").Append(httpContext.Connection.RemoteIpAddress).Append("<br/>");
                }
                EmailException(exception, stringBuilder);
            }
        }

        private void EmailException(Exception exception, StringBuilder stringBuilder)
        {
            BuildExceptionText(stringBuilder, "<h1>Error </h1>", exception);

            _emailSender.SendMailAsync(
                _smtpDetails.Value, new List<string> { _configuration["ErrorDeliveryEmailAddress"] },
                "System Error", stringBuilder.ToString()).ConfigureAwait(false);
        }

        private StringBuilder BuildExceptionText(StringBuilder stringBuilder, string title, Exception exception)
        {
            stringBuilder.Append(title).Append("<h2>").Append(exception.Message).Append("</h2><br/>")
               .Append(exception.Source ?? "").Append("<hr/>");
            if (exception.StackTrace != null)
            {
                stringBuilder.Append("<h3>Stack trace: </h3><br/>").Append(exception.StackTrace.Replace(Environment.NewLine, "<br/>"));
            }

            if (exception.InnerException != null)
            {
                BuildExceptionText(stringBuilder, "<h2>Inner exception </h2>", exception.InnerException);
            }

            return stringBuilder;
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return new NoDispose();
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }
    }

    [Serializable]
    internal class NoDispose : IDisposable
    {
        public void Dispose()
        {
        }
    }

I am using my own IEmailSender class, you should replace this with your own email functionality. I stored the SMTP details and the Error Delivery Email Address in my appsettings.json file.

"smtpDetails": {
    "host": "",
    "username": "",
    "password": "",
    "port": 25,
    "enablessl": true,
    "domain": "",
    "senderEmail": "",
    "senderName": ""
  },
  "ErrorDeliveryEmailAddress": "james@jdono.com"

There are some dependencies here on Asp.Net Identity and the HttpContext accessor, if you’re not interested in providing the email recipient with the Username or request address details you can remove these, or you could add more information from the request as desired.

Emailing HTTP 4xx/5xx responses

Using the above implementation there is a quick and easy way to log HTTP client errors and server errors.

Add the following to startup.cs:

if (env.IsDevelopment())
{
    // ...
}
else
{
    // ...
    app.UseStatusCodePagesWithReExecute("/error/{0}");
}

And in your Home controller add a method to deal with errors.

[Route("/Error/{statusCode}")]
public IActionResult Error(int statusCode)
{
    LogHttpError(statusCode);
    ViewData["StatusCode"] = statusCode;
    return View();
}

private void LogHttpError(int statusCode)
{
    var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
    var exception = new Exception($"Http {statusCode} returned when accessing {feature.OriginalPath}/{feature.OriginalQueryString}");
    _logger.LogError(exception, exception.Message);
}

This will raise an error which will in turn execute our EmailLogger. Clearly, this solution may not be to everyones’ taste as HTTP client errors (4xx) are often dealt with seperately and perhaps wouldn’t be emailed to your development/support team. It would, however, be pretty easy to abstract that away.

Email Logger in action

So with all of the above, when there’s a production error in my application I’m emailed as expected.

I’m also emailed for a 404 response..

So we’re all set. If you have any questions or comments on this implementation please feel free to leave discuss below.

Seeding an Asp.Net Core 2 Code First Database with Data and User Accounts

Entity Framework Core

I had a requirement to seed an enterprise application with some data in an Asp.Net Core 2.0 application. There is no native support and the most popular solutions elsewhere on the web were either overly-complicated, or involved manipulating the Main method. 

In the end I wrote a solution to call a seeding method from the Configure class in Startup.cs, like this: 

app.SeedDatabase();

The Configure class is generally used to set up your HTTP middleware, however I personally preferred this solution as it looks neater and ties in with the rest of the startup functionality in my web application. 

Seeding Data 

First of all you need to extend the IApplicationBuilder class. I placed this in a class inside a ‘Middleware’ folder with my other extension methods of this class. The convention is to include this in the Microsoft.AspNetCore.Builder namespace, but I’ll leave that decision to you.

public static class ApplicationBuilderExtensions 
{
    public static IApplicationBuilder SeedDatabase(this IApplicationBuilder app) 
    { 
        IServiceProvider serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider; 
        try 
        { 
            var context = serviceProvider.GetService<MyContext>(); 
            DatabaseSeeder.InsertSeedData(context); 
         } 
        catch (Exception ex) 
        {
            var logger = serviceProvider.GetRequiredService<ILogger<Program>>(); 
            logger.LogError(ex, "An error occurred while seeding the database."); 
        } 
        return app; 
    } 
}

Next, you need to create a class and method to call your seeding logic. Here I am checking if a table is empty, if it’s empty then I populate some default data. This is great when deploying your app to a new environment.  

public static void InsertSeedData(MyContext context) 
{ 
    if (!context.UserTypes.Any()) 
    { 
        context.UserTypes.AddRange( 
                new UserType { Name = "Employee" }, 
                new UserType { Name = "Client" }, 
                new UserType { Name = "Supplier" } 
                ); 

        context.SaveChanges(); 
    } 
}

Finally, we just need to call the extended method from Configure in Startup.cs: 

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    // … Your Middleware config goes here 

    app.UseMvcWithDefaultRoute(); 

    app.SeedDatabase(); 
}

Seeding User Accounts 

Of course, many developers like to seed a default admin account, as once your authorisation is in place it can cause frustrations if you don’t have any user accounts to login with. 

One of the main concerns is how to avoid hard-coding the admin password into the method thus checking it in to source control. 

.Net Core has a handy way of dealing with this: any sensitive data can be kept in an appsettings.development.json file, which you can exclude from source control. 

First of all, let’s set up this file. If you don’t have an appsettings.development.json file (or equivalent), you can simply copy your existing appsettings.json and re-name it. 

Once that’s in place you need to add your desired admin account details to the file. 

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=jdono;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "AdminAccount": {
    "username": "sysadmin",
    "password": "jdono.com1",
    "role": "sysadmin",
    "email":  "james@jdono.co.uk"
  }
}

We will need to ask the resolver for aIConfiguration to use the built in appsettings configuration features and a UserManager/RoleManager to deal with the User/Roles stuff.  

// ..
var configuration = serviceProvider.GetService<IConfiguration>(); 
var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>(); 
var roleManager = serviceProvider.GetService<RoleManager<ApplicationRole>>(); 
DatabaseSeeder.InsertDefaultAdminAccount(configuration, userManager, roleManager); 
// ..

Here’s how it now looks in our SeedDatabase method: 

public static IApplicationBuilder SeedDatabase(this IApplicationBuilder app) 
{ 
    IServiceProvider serviceProvider = app.ApplicationServices.CreateScope().ServiceProvider; 
    try 
    { 
        var context = serviceProvider.GetService<MyContext>(); 
        DatabaseSeeder.InsertSeedData(context); 

        var configuration = serviceProvider.GetService<IConfiguration>(); 
        var userManager = serviceProvider.GetService<UserManager<ApplicationUser>>(); 
        var roleManager = serviceProvider.GetService<RoleManager<ApplicationRole>>(); 
        DatabaseSeeder.InsertDefaultAdminAccount(configuration, userManager, roleManager); 
    } 
    catch (Exception ex) 
    { 
        var logger = serviceProvider.GetRequiredService<ILogger<Program>>(); 
        logger.LogError(ex, "An error occurred while seeding the database."); 
    } 

    return app; 
}

As far as I can see from the documentation (and please correct me if I’m wrong) the IServiceProvider deals with disposing the services it called using dependency injection so we don’t need to manually call dispose, or use a using statement. 

Finally, let’s create the InsertDefaultAdminAccount method to deal with inserting the role and the user if they don’t exist, and adding the user to the role. 

public static async void InsertDefaultAdminAccount( 
          IConfiguration configuration, 
          UserManager<ApplicationUser> userManager, 
          RoleManager<ApplicationRole> roleManager) 
{ 
    string username = configuration["AdminAccount:username"]; 
    string email = configuration["AdminAccount:email"]; 
    string password = configuration["AdminAccount:password"]; 
    string role = configuration["AdminAccount:role"]; 

    if (!(await roleManager.RoleExistsAsync(role))) 
    { 
        await roleManager.CreateAsync(new ApplicationRole { Name = role }); 
    } 

    //create the default admin account 
    if (!userManager.Users.Any(u => u.Name == username)) 
    { 
        var user = new ApplicationUser() { UserName = username, Email = email }; 
        var result = await userManager.CreateAsync(user, password); 

        if (result.Succeeded) 
        { 
            await userManager.AddToRoleAsync(user, role); 
        } 
    } 
}

Credit goes to Emmanuel for some of the code used in the SeedDatabase method.