-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Authentication overview
Explaining the authentication support in Nancy is pretty easy - there really is not any. That is not to say that you cannot do authentication with Nancy, or that it is not on the “super duper happy path”, but at the core Nancy contains only some very basic constructs around authentication, which other packages (or your own) can take advantage of to provide many types of authentication.
Instead of shipping inflexible, hard coded, “built in” authentication providers, the shipped authentication facilities takes advantage of extension points in Nancy, such as Application pipelines, Module pipelines, NancyContext
and normal extension methods to provide the desired functionality.
What this means is that if the default implementations we ship in the box do not suit your business needs, you can simply write your own without having to worry about conforming to a complex set of predefined interfaces, base classes, or other constraints that a hard coded authentication awareness would impose on you.
Out-of-the-box we ship the following authentication mechanisms, each of which is activated by installing the corresponding (name in the parenthesis) Nuget package.
- Forms (
Nancy.Authentication.Forms
) - Basic (
Nancy.Authentication.Basic
) - Stateless (
Nancy.Authentication.Stateless
)
Even though Nancy does not know anything about authenticating a user, it still understands the concept of a user. In Nancy a user is defined by the IUserIdentity
interface, which provides the basic information about a user's identity
public interface IUserIdentity
{
/// <summary>
/// Gets or sets the name of the current user.
/// </summary>
string UserName { get; set; }
/// <summary>
/// Gets or set the claims of the current user.
/// </summary>
IEnumerable<string> Claims { get; set; }
}
This interface should be implemented by you to provide a domain specific user object that contains information relevant to your application.
To gain access to the current authenticated user, you simply access the CurrentUser
property on the NancyContext
. A value of null
means that the user of the current request has not been authenticated, anything else means the user has been authenticated.
The context should be available in most places of Nancy so it should never be an issue to get the identity of the user that is invoking the current request.
Establishing the identity of the current user is only half of the equation. You also need to have a way to secure resources in your application, so that they are only permitted to be accessed by authenticated users and perhaps even users that meet certain claims.
Resources are secured at a module or route level (of course, you could secure all resources by creating an [application pipeline](The Application Before, After and OnError pipelines) if you have the need) by inspecting the NancyContext.CurrentUser
property and making sure that it is not null.
This task can be performed in a Before
hook on the [module pipeline](The before and after module hooks), which ensures it executed before any route in the module is invoked. The hook also gives us the ability to prematurely terminate the execution of the request and return another response, as is required when an unauthenticated user is trying to access a secure resource.
public class SecureModule : NancyModule
{
public SecureModule()
{
Before += ctx => {
return (this.Context.CurrentUser == null) ? new HtmlResponse(HttpStatusCode.Unauthorized) : null;
};
// Your routes here
}
}
Optionally you would also verify the claims of the user, but that is the basic gist of how to secure resources in a module.
Adding that code to each of your secure modules is a violation of the DRY principle and is, quite frankly, a tedious task. Enter extension methods!
Nancy comes with a couple of extension methods that encapsulate these tasks and drastically cut down the amount of code you have to write.
The following extension methods are available
-
RequiresAuthentication
- Ensures that an authenticated user is available or it will returnHttpStatusCode.Unauthorized
. TheCurrentUser
must not benull
and theUserName
must not be empty for the user to be considered authenticated. -
RequiresClaims
- Enables you to define the list of claims that the user has to meet in order to be granted access to the secured resources. All listed claims have to be met for access to be granted -
RequiresAnyClaim
- Same asRequiresClaims
but with the difference that at least one of the listed claims has to be met in order for access to be granted -
RequiresValidatedClaims
- Gives you full control over the claims validation process, by letting you define a function, in the formatFunc<IEnumerable<string>, bool>
that is invoked to determine if access should be granted or not -
RequiresHttps
- While this is not strictly related to authentication, it tells Nancy that the resources are only permitted to be accessed if the request uses https. There is an optional bool parameter that can be used to control if the user should be redirected to the resources, as a https request, if an attempt is made to access it using http. This can be used to ensure that resources are always accessed using a secure request
All of these are extension methods on the NancyModule
class, which makes it trivial to add your own or to mimic the behaviors on an application level. You need to include the types in the Nancy.Security
namespace in order for these extension methods to be available from inside your module.
Using these extension methods, the previous example can be updated to look like the following code
public class SecureModule : NancyModule
{
public SecureModule()
{
this.RequiresAuthentication();
}
// Your routes here
}
Of course they can be combined as well
public class SecureModule : NancyModule
{
public SecureModule()
{
this.RequiresHttps();
this.RequiresAuthentication();
this.RequiresClaims(new [] { "Admin" });
}
// Your routes here
}
All of these can also be used on a per-route level
public class SecureModule : NancyModule
{
public SecureRoute()
{
Get["/"] = _ =>
{
this.RequiresAuthentication();
return 200;
};
}
}
To be granted permission to the routes in the SecureModule
above, the user has to be accessing it using https, be authenticated, and have the Admin claim. If either of these are untrue then permissions will be denied.
To create your own security extensions you simply add extension methods to the NancyModule
that attaches to the Before
pipeline and inspects the credentials.
For example, this is how RequiresAuthentication
works
public static class ModuleSecurity
{
public static void RequiresAuthentication(this NancyModule module)
{
module.Before.AddItemToEndOfPipeline(RequiresAuthentication);
}
private static Response RequiresAuthentication(NancyContext context)
{
Response response = null;
if ((context.CurrentUser == null) ||
String.IsNullOrWhiteSpace(context.CurrentUser.UserName))
{
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
}
return response;
}
}
This makes sure that the CurrentUser
has been set and that it has a valid UserName
in which case it will return null
, meaning that the execution should continue as normal. Invalid credentials will result in a response, with the HttpStatusCode.Unauthorized
status code, to be returned which will terminate the current request prematurely, thus it will never reach a route.
The actual implementation of an authentication provider is going to vary depending on your requirements but the basic pattern goes as follows
- A
Before
hook on the [application pipeline](The Application Before, After and OnError pipelines) is used to check for the existence of valid credentials for the incoming request (such as a cookie, headers and so on). If found it will authenticate the user and assign it to theCurrentUser
property onNancyContext
- A
Before
hook on the [module pipeline](The before and after module hooks) is used to establish that the current request is being performed by an authenticated user. If not then it should deny access and return something likeHttpStatusCode.Unauthorized
- An
After
hook on the [application pipeline](The Application Before, After and OnError pipelines) will check for a request that was aborted due to failed authentication, such as keeping an eye out for a response with theHttpStatusCode.Unauthorized
(401) status code. If detected it will take steps to help the user get authenticated, such as redirecting to a login form or perhaps signal to the client with help of headers
Your mileage may vary but that is the basic flow of an authentication provider.
« Part 21. Extending Serialization with Converters — Documentation overview — Part 22. Stateless »
- Introduction
- Exploring the Nancy module
- Routing
- Taking a look at the DynamicDictionary
- Async
- View Engines
- Using Models
- Managing static content
- Authentication
- Lifecycle of a Nancy Application
- Bootstrapper
- Adding a custom FavIcon
- Diagnostics
- Generating a custom error page
- Localization
- SSL Behind Proxy
- Testing your application
- The cryptography helpers
- Validation
- Hosting Nancy with ASP.NET
- Hosting Nancy with WCF
- Hosting Nancy with Azure
- Hosting Nancy with Suave.IO
- Hosting Nancy with OWIN
- Hosting Nancy with Umbraco
- Hosting Nancy with Nginx on Ubuntu
- Hosting Nancy with FastCgi
- Self Hosting Nancy
- Implementing a Host
- Accessing the client certificate when using SSL
- Running Nancy on your Raspberry Pi
- Running Nancy with ASP.NET Core 3.1