:::: MENU ::::

Monday, May 29, 2017

This repository is a simple example of how to use ASP.NET Core Identity without Entity Framework.
I created it to help answer this StackOverflow question back in the early days of ASP.NET Core.
It just uses an in-memory list to store the user data, so it's not intended for real-world use. It's just a simple example to show how the various parts fit together, so that you can use it for inspiration when building your own system.

Going way back to, I think, .NET v3, ASP.NET had this new thing called Membership. Maybe it was a version earlier. I dunno. "Neat," I thought, I can write a provider adhering to this interface and use my existing user and auth structure to plug into this system. Then I saw that the membership and role providers each had about a bazillion (maybe quadbazillion) members to implement, and reality set in that what I already had was working just fine. Some years later, ASP.NET offered Identity, this newer thing that did sort of the same thing. It even made its way into Core.
You don't need it. For real. I'm not saying that it isn't a useful piece of the framework, but you need to stop making it the default for user management. It's not hard or time consuming to build out your own system of user entities and permissions (roles, claims, etc.) as you see fit. The problem, as I see it, is that developers are confusing the act of persisting user information with authentication. I get why that may be, as Identity uses one line of code to both verify a user and sign them in (Core docs show how). But under the covers, there is code that first verifies the user/password against the database, then sets the auth cookie to indicate who the user is for future requests. You can in fact do one without the other.
Why would you do that? Part of it may just be an issue of control, but for me, it's because I want to be very specific about how I structure my user data. I also don't really want to use Entity Framework in many cases (read: most things I port from older apps), and EF is part of the magic of Identity. What I've seen in a number of projects is the use of Identity mixed with a home-grown set of user domain objects and a totally separate database or persistence mechanism. If you're doing all of that plumbing anyway, you definitely don't need the additional overhead of Identity.
Let's use ASP.NET Core as an example, first. In Startup, we use the Configure method to use cookie-based authentication:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
   AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,
   AutomaticAuthenticate = true
});
In some kind of login method, from our MVC controller, we look up the user in the code that we wrote, with whatever backing store we made, and then sign in. Let's pretend that myUser is some construct we've made up:
var myUser = _myUserLookerUpperService(email, password);
var claims = new List
{
   new Claim(ClaimTypes.Name, myUser.Name)
};
var props = new AuthenticationProperties
{
   IsPersistent = persistCookie,
   ExpiresUtc = DateTime.UtcNow.AddYears(1)
};

var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), props);
The code should be pretty straightforward. Whatever our domain-specific user thingy is, it's something built for us, instead of the generic thing that the Identity framework has created. We use that to construct a set of claims and authentication properties, and then use the built-in Authentication system to sign in with our newly constructed principal. This is what creates the encrypted cookie on the user's browser. It's not as magic as the Identity service, but remember that you're welcome to use any kind of schema that you want to persist user data, and that means you can query it or normalize it (if you must) against any other bits of data you have.
Naturally, you may want to set up some other context, or simply verify that they're still a known-good user on each request. To do that, you can wireup middleware in the Startup's Config method (app.UseMiddleware();). Middleware doesn't use an interface (and I don't know why they chose convention over an interface), but it does expect an Invoke method to do stuff. It's here that you would look up the user based on the identity:
public class MyMiddleware
{
   private readonly RequestDelegate _next;

   public MyMiddleware(RequestDelegate next)
   {
      _next = next;
   }

   public async Task Invoke(HttpContext context)
   {
      var name = context.User.Identity.Name;
      if (!string.IsNullOrWhiteSpace(name))
      {
         var userService = context.RequestServices.GetService();
         var user = userService.GetUserByName(name);
         if (user != null)
         {
            // do stuff here
         }
         else
         {
            // do something about your bad user
         }
      }
      await _next(context);
   }
}
Again, I believe that the Identity framework has some plumbing for this, but if you're a control freak like me, this is better. The official documentation has a really great write up on using this cookie mechanism without Identity.
If you're still using ASP.NET 4.5 and MVC on top of it (or even WebForms), you don't need to use Identity here either. In your MVC action, or your event handler in WebForms, you can use Forms Authentication to do the same work, without any setup (though you can change the cookie name and some other things via web.config):
var user = _myUserLookerUpperService(email, password);
var ticket = new FormsAuthenticationTicket(1, user.Name, DateTime.Now, DateTime.Now.AddDays(30), createPersistentCookie, "");
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.Expires = DateTime.Now.AddDays(30);
context.Response.Cookies.Add(cookie);
Neat, right? The static FormsAuthentication class also has a SignOut method. Instead of middleware, we can use an IHttpModule or an IActionFilter to act on user data as appropriate.
To circle back, the point here is that Identity is great to spin up some user account persistence and authentication quickly, but if you want to do your own thing, or don't want EF involved, or you're a control freak, understand that you don't need Identity to auth your users.
EDIT 9/6/16: Andrew Lock has a pretty solid outline of how claims-based identities should be used. He goes way more in depth about creating a new principal and signing in the user, but note that he's not getting into the business of persistence here.

Saturday, May 27, 2017

This short post is in response to a comment I received on a post I wrote a while ago, about how to set the hosting environment in ASP.NET Core. It's a question I've heard a couple of times, so thought I'd write it up here.
The question by Denis Zavershinskiy is as follows:
Do you know if there is a way to overwrite environment variable name? For example, I want my CoolProject to take environment name not from ASPNETCORE_ENVIRONMENT but from COOL_PROJ_ENV. Is it possible?
The answer to that question is a little nuanced. If you already have an app deployed, and want to switch the environment for it without changing other apps on that machine, then you can't do it with Environment variables. Those obviously affect the whole environment!
tl;dr; Create a custom configuration object in your Program.cs file, load the environment variable using a custom key, and call UseEnvironment on the WebHostBuilder.
However, if this is a capability you think you will need, you can use a similar approach to the one I use in that post to set the environment using command line arguments.
This approach involves building a new IConfiguration object, and passing that in to the WebHostBuilderon application startup. This lets you load configuration from any source, just as you would in your normal startup method, and pass that configuration to the WebHostBuilder using UseConfiguration. The WebHostBuilder will look for a key named "Environment" in this configuration, and use that as the environment.
For example, if you use the following configuration.
var config = new ConfigurationBuilder()  
    .AddCommandLine(args)
    .Build();

var host = new WebHostBuilder()  
    .UseConfiguration(config)
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseKestrel()
    .UseIISIntegration()
    .UseStartup<Startup>()
    .Build();
You can pass any setting value with this setup, including the "environment variable":
> dotnet run --environment "MyCustomEnv"

Project TestApp (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.

Hosting environment: MyCustomEnv  
Content root path: C:\Projects\Repos\MyCoolProj\src\MyCoolProj  
Now listening on: http://localhost:5000  
Application started. Press Ctrl+C to shut down.  
This is fine if you can use command line arguments like this, but what if you want to use environment variables? Again, the problem is that they're shared between all apps on a machine.
However, you can use a similar approach, coupled with the UseEnvironment extension method, to set a different environment for each machine. This will override the ASPNETCORE_ENVIRONMENT value, if it exists, with the value you provide for this application alone. No other applications on the machine will be affected.
public class Program  
{
    public static void Main(string[] args)
    {
        const string EnvironmentKey = "MYCOOLPROJECT_ENVIRONMENT";

        var config = new ConfigurationBuilder()
            .AddEnvironmentVariables()
            .Build();

        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseEnvironment(config[EnvironmentKey])
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseApplicationInsights()
            .Build();

        host.Run();
    }
}
To test this out, I added the MYCOOLPROJECT_ENVIRONMENT key with a value of Staging to the launch.json file VS uses when running the app:
{
  "profiles": {
    "EnvironmentTest": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "MYCOOLPROJECT_ENVIRONMENT": "Staging"
      },
      "applicationUrl": "http://localhost:56172"
    }
  }
}
Running the app using F5, shows that we have correctly picked up the Staging value using our custom environment variable:
Hosting environment: Staging  
Content root path: C:\Users\Sock\Repos\MyCoolProj\src\MyCoolProj  
Now listening on: http://localhost:56172  
Application started. Press Ctrl+C to shut down.  
With this approach you can effectively have a per-app environment variable that you can use to configure the environment for an app individually.

Summary

On shared hosting, you may be in a situation when you want to use a different IHostingEnvironment for multiple apps on the same machine. You can achieve this with the approach outlined in this post, building an IConfiguration object and passing a key to WebHostBuilder.UseEnvironment extension method.