CHAPTER 11
If you're using a web framework to build web services to be used by the general public, then it's likely you'll need some way of controlling who has access to which facilities in said service.
It may be as simple as ensuring that API users provide a key in the headers before they can access your API, or it may that you’re building an entire platform that has to support multiple users of different access levels.
Yet again, Nancy has all the bases covered here, and implementing an authentication scheme is very easy. As with many of the other features, everything is modular and installable using NuGet, and in the case of the authentication stuff, nothing is installed by default.
Before I give you an example, I'm going to go over the three methods that are available to install from NuGet. You can find them all by searching for “Nancy.Authentication” and installing the package you want.
While it's perfectly fine to install more than one method, there isn’t much documentation on running two of them side-by-side. My recommendation is to stick with one method at a time.
Given what I've read however, it should be possible to implement two or more methods at the same time if you really did need to do so.
So what's available? If you look on NuGet, you'll see the following methods listed: Stateless, Forms, and Token. Each of these methods has its strengths and its weaknesses, but more importantly, each is designed to be used in a specific scenario, as you'll see next.
We'll start with the easiest one first. If you're used to anything in ASP.NET, it should be forms authentication.
Used since the days of the original WebForms implementation, Forms authentication has been (and still continues to be) the underpinning of the authentication on the vast majority of .NET-based web applications built in the last 10 years.
It comes as no surprise, then, that Nancy implements this in exactly the same way as the other implementations you're used to. Later in this chapter I'll run through an example of how to use it, but for now, if you're used to using and configuring this for web sites that are designed to be used by human beings, then this should be your first choice in your Nancy application.
Its primary motivation is interactive web sites and web applications, interacted with by a human operator through a standard web browser-style interface.
Stateless authentication is primarily designed for use by API-based applications or applications that need to check and authenticate on each request.
as an example of the intended use of this method, the NancyFX documentation gives an API endpoint with access key provided in the request headers. You could, however, use it for many other service methods.
Its use is very simple; unlike the other methods, there's no configuration other than a simple handler that needs to be performed to get things working.
The handler that you define to use the method will be called each time an endpoint secured using the method is called. The handler should then either return Null if the request should not be allowed, and a Nancy IUserIdentity if the request is considered valid and should be allowed (we’ll learn more about this in a moment).
The Token method is probably the most complex of the methods to understand.
The documentation states that its primary use is for multiple consuming clients such as mobile apps, single-page apps, and others that should allow authentication for future requests, but not have to re-query the backend data store once a user has successfully authenticated the first time.
There is a better description of its intended usage on the NancyFX Wiki, so I won’t go into the description in much depth here.
The method works by keeping a dual key store inside the Nancy app, which it checks against for each protected resource.
The initial user authentication is performed by a custom method provided by the application writer. Once this is performed the first time for a given user, subsequent requests then just use the tokens generated by the method, which are valid for any given 24-hour period before timing out.
Irrespective of which method of authentication you choose, or even if you use your own custom provider, there are some concepts that are common to all of them.
The first concept is that of a user. Nancy understands what a user is and how the idea of having a user included as part of the request works.
At any point in your Nancy application where you have access to the NancyContext, you will also have the ability to read a property called CurrentUser.
If the CurrentUser property is null, then the request is not considered to be an authenticated one. If the property has an object attached to it that derives from the IUserIdentity interface implemented in the Nancy core, then the request is considered authenticated.
At minimum, the CurrentUser property should implement a string containing the users name and an enumerable list of strings containing a list of user claims.
If these are set, you can then begin to secure your application by attaching a Before pipeline filter to your request; in this filter you need only check the current user and act accordingly. You will find out more about these in the next couple of chapters.
Nancy makes adding this filter much simpler by providing some premade extension methods specifically for handling your applications authentication needs.
The simplest of these is RequiresAuthentication, which will kick back an HTTP response of 403 (Not Authorized) if the user property is null.
You can also have RequiresClaims, which allows you to match a list of claims against the list of claims provided by the user identity, allowing you to provide different levels of access depending on the user’s abilities. Be aware that if you use anything other than RequiresAuthentication, most of it will call this first, so even though you can specify multiple, you often will not explicitly have to call RequiresAuthentication in most cases.
RequiresAnyClaim is similar to RequiresClaims, with the difference being that at least one should match and not all. For example if your user has Admin, Commenter, User and you ask for RequiresClaims listing Admin, User, then both of those claims must be present. RequestAnyClaim will validate on either Admin or User (or both) being present.
RequiresValidatedClaims validates against the claims list attached to the user, but instead of comparing existing data, it requires a lambda pointing to a function that will check and verify the claims in real time.
The final extension, RequiresHttps, allows the module to be called only if it's being called via a secure HTTP connection, and will refuse access if it's not.
Each of these extensions are called on your Nancy routing module inside the constructor just before you define your routes, and more than one can be called. Everything you need at a base level is defined in Nancy.Security.
With this information under your wing, securing your application becomes adding one or more of the extension methods to your module, as the following code shows:
Code Listing 47
using Nancy; using Nancy.Security; namespace nancybook.modules { public class AuthRoutes : NancyModule { public AuthRoutes() : base("/auth") { this.RequiresAuthentication(); Get[@"/"] = _ => View["auth/index"]; } } } |
In this case, all that is required is that an authenticated user be present; you can add as many of the others as required to control level of security you require in your application.
Once you've secured your module, you then need to install and set up one of the available three authentication methods, or write your own.
For the remainder of this chapter, I will show you how to get forms authentication up and running.
Implementing forms authentication is quite straightforward once you understand how everything works. However, you will need to create a custom bootstrap class, which we have not yet covered.
We'll learn more about what exactly a Bootstrap class is in the next chapter; for now, just follow the instructions to create it, and all will be revealed soon.
Open up your NuGet package manager, and search for and install Nancy.Authentication.Forms. Once you have this installed, create a new class in the root of your application called CustomBootstrapper.cs and make sure it has the following contents (remember to adjust things like the namespace as needed for your project):
Code Listing 48
using Nancy; using Nancy.Authentication.Forms; using Nancy.Bootstrapper; using Nancy.TinyIoc; namespace nancybook { public class CustomBootstrapper : DefaultNancyBootstrapper { protected override void ConfigureRequestContainer( TinyIoCContainer container, NancyContext context) { base.ConfigureRequestContainer(container, context); container.Register<IUserMapper, FakeDatabase>(); } protected override void RequestStartup( TinyIoCContainer container, IPipelines pipelines, NancyContext context) { base.RequestStartup(container, pipelines, context); var formsAuthConfiguration = new FormsAuthenticationConfiguration { RedirectUrl = "~/account/login", UserMapper = container.Resolve<IUserMapper>() }; FormsAuthentication.Enable(pipelines, formsAuthConfiguration); } } } |
Save and close this bootstrapper class; we won't need to do anything else with it for now.
You now need to create a class that provides an IUserIdentity as a result for the authentication calls to query. In this case, we have told Nancy that we’re going to create a FakeDatabase class to provide it with this information. In reality, we would implement a full way of communicating with a backend data store, and finding an appropriate user's ID. For our purposes, we'll just create a static list of users and allow our app to query against that.
Create a new class called FakeDatabase.cs, and make sure it has the following code in it:
Code Listing 49
using System; using System.Collections.Generic; using System.Linq; using Nancy; using Nancy.Authentication.Forms; using Nancy.Security; namespace nancybook { public class DatabaseUser : IUserMapper { private List<FakeDatabaseUser> _users = new List<FakeDatabaseUser> { new FakeDatabaseUser { UserId = new Guid(01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11), UserName = "admin", Claims = new List<string> {"Admin", "Reader", "Writer"}, Password = "admin" }, new FakeDatabaseUser { UserId = new Guid(02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12), UserName = "alice", Claims = new List<string> {"Reader", "Writer"}, Password = "letmeout" }, new FakeDatabaseUser { UserId = new Guid(03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13), UserName = "bob", Claims = new List<string> {"Reader"}, Password = "letmein" } }; public IEnumerable<FakeDatabaseUser> Users { get { return _users; }} public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) { var user = _users.FirstOrDefault(x => x.UserId == identifier); return user == null ? null : new AuthenticatedUser { UserName = user.UserName, Claims = user.Claims }; } } } |
We also need classes for FakeDatabaseUser and AuthenticatedUser:
FakeDatabaseUser.cs
Code Listing 50
using System; using System.Collections.Generic; namespace nancybook { public class FakeDatabaseUser { public Guid UserId { get; set; } public string UserName { get; set; } public List<string> Claims { get; set; } public string Password { get; set; } } } |
AuthenticatedUser.cs
Code Listing 51
using System.Collections.Generic; using Nancy.Security; namespace nancybook { public class AuthenticatedUser : IUserIdentity { public string UserName { get; set; } public IEnumerable<string> Claims { get; set; } } } |
With these three classes in place, and the custom bootstrapper setting things up, we should now have everything we need in place to add the authentication into our application.
For the purposes of receiving the login information from an HTML view that implements a form for the user to enter login credentials, we need to create a suitable view model. I've defined this to be:
Code Listing 52
using System.ComponentModel.DataAnnotations; namespace nancybook.Models { public class LoginParams { [Required] public string LoginName { get; set; } [Required] public string Password { get; set; } } } |
In a file called LoginParams.cs, there's no requirement to model this in any specific way. It's up to you what you provide from your actual client-side experience; all you need to remember is that your user identity in the backend must have a GUID as part of its stored data, and that you must be able to retrieve that GUID and pass it to Nancy when you authenticate the login form.
I'll let you create your own views to implement the various screens needed, but you'll need one to provide a login form, and one to display a login failed message. You'll also need a view for your secure part of the application that will be displayed when the user successfully logs in.
Once you have the views in place, the last thing you need are a couple of Nancy routing modules. The first one is your secure area:
Code Listing 53
using Nancy; using Nancy.Security; namespace nancybook.modules { public class AuthRoutes : NancyModule { public AuthRoutes() : base("/auth") { this.RequiresAuthentication(); Get[@"/"] = _ => View["auth/index"]; } } } |
The second module is a route to provide the ability for the user to log in and log out of the system:
Code Listing 54
using System.Linq; using nancybook.Models; using Nancy; using Nancy.Authentication.Forms; using Nancy.ModelBinding; namespace nancybook.modules { public class AccountRoutes : NancyModule { public AccountRoutes() : base("/account") { Get[@"/login"] = _ => View["account/login"]; Post[@"/login"] = _ => { var loginParams = this.Bind<LoginParams>(); FakeDatabaseUser user; FakeDatabaseUser db = new FakeDatabaseUser(); user = db.Users.FirstOrDefault( x => x.UserName.Equals(loginParams.LoginName) && x.Password.Equals(loginParams.Password)); if(user== null) { return View["account/loginerror"]; } return this.Login(user.UserId, fallbackRedirectUrl: "~/auth"); }; Get[@"/logout"] = _ => this.LogoutAndRedirect("~/account/login"); } } } |
At this point, you should be able to build your project. Browsing to /auth should trigger the login page, allowing you to enter one of the user names and passwords in the fake database class, which in turn should allow you to access the routes in the Auth module.
In this chapter, you learned how Nancy's authentication features work, and saw that it's very easy to implement authentication in any way you need to.
You also learned that there are three common scenarios and authentication methods already defined for implementation, and ready to install using NuGet.
In the next chapter, we'll start to get under the hood of the engine that drives NancyFX. We’ll learn what a customized bootstrap class is, and how to use it.