Skip to content

NancyFX Manual Setup

Johan Nilsson edited this page Nov 15, 2015 · 4 revisions

Manual setup of WorldDomination Web Authentication is done by creating your own routes to handle the redirect and callback when authenticating with a provider.

This differs from the easier setup using the Nancy.Authentication.WorldDomination package which automatically does this for you. This setup will give you an idea of what that package does for you.

Installing

First up, start by installing the package WorldDomination.Web.Authentication to your Nancy project.

PM> Install-Package WorldDomination.Web.Authentication

You can find the Nuget package here: http://nuget.org/packages/WorldDomination.Web.Authentication/

Configuring WorldDomination.Web.Authentication

There's two ways to configure the library, the web.config Configuration way is the same for both MVC and NancyFX. So this will be shown using the Bootstrapper.

In your Bootstrapper, override the ConfigureApplicationContainer, create an instance of your

public class SampleBootstrapper : DefaultNancyBootstrapper
{
    private const string TwitterConsumerKey = "*key*";
    private const string TwitterConsumerSecret = "*secret*";
    private const string FacebookAppId = "*key*";
    private const string FacebookAppSecret = "*secret*";
    private const string GoogleConsumerKey = "*key*";
    private const string GoogleConsumerSecret = "*secret*";

    protected override void ConfigureApplicationContainer(TinyIoCContainer container)
    {
        base.ConfigureApplicationContainer(container);

        var twitterProvider = new TwitterProvider(TwitterConsumerKey, TwitterConsumerSecret);
        var facebookProvider = new FacebookProvider(FacebookAppId, FacebookAppSecret);
        var googleProvider = new GoogleProvider(GoogleConsumerKey, GoogleConsumerSecret);

        AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory();

        providerFactory.AddProvider(twitterProvider);
        providerFactory.AddProvider(facebookProvider);
        providerFactory.AddProvider(googleProvider);
    }   
}

Linking in your view

WorldDomination.Web.Authentication doesn't have a magical UI for you to slap into your website, you need to do this yourself, and all it requires is some links, this means you get full control and flexibility over creating a UI and don't have to try accommodate to some crappy HTML we could have come up with.

In your UI you will want to create some anchor tags that link to your Redirect route.

Assuming your route will be:

/authentication/redirect/{providerkey}

Create some links in your view like so:

<a href="/authentication/redirect/twitter">Login with Twitter</a>
<a href="/authentication/redirect/facebook">Login with Facebook</a>
<a href="/authentication/redirect/google">Login with Google</a>

You can use images, text, what ever you want, so long as you link the user to the correct route, everything will be fine.

Creating the module

The module will need to accept the IAuthenticationService created in the Bootstrapper.

public class AuthenticationModule : NancyModule
{
    private const string StateKey = "WorldDomination-StateKey-cf92a651-d638-4ce4-a393-f612d3be4c3a";

    public AuthenticationModule(IAuthenticationService authenticationService)
    {
    }
}

For a little bit of extra security you can create a StateKey, this is used to create a session variable to ensure that the callback belongs to the user who just logged in. The above is created with a random Guid. You can copy the above one but it's recommended you create your own.

Don't call Guid.NewGuid(), if you put that in a load-balanced system you would end up creating a different Guid on each instance of the application.

Creating the redirection route

Using the same pattern above, create a route in your module to point to /authentication/redirect/{providerkey}

Get["/authentication/redirect/{providerkey}"] = _ =>
{
    if (string.IsNullOrEmpty((string)_.providerkey))
    {
        throw new ArgumentException(
            "You need to supply a valid provider key so we know where to redirect the user.");
    }
    
    var settings = authenticationService.GetAuthenticateServiceSettings((string)_.providerkey);
    var guidString = Guid.NewGuid().ToString();

    Session[StateKey] = guidString;
    settings.State = guidString;
    settings.CallBackUri = GetReturnUrl("/authentication/authenticatecallback", (string)_.providerkey);

    Uri uri = authenticationService.RedirectToAuthenticationProvider(settings);

    return Response.AsRedirect(uri.AbsoluteUri);
};

This code checks to make sure that the request contained a provider key, it then attempts to get the settings for that provider.

Once it has the settings it can create a callback Uri , and assign a state key to the request. The provider settings then creates a Uri for you to redirect to.

Simply response with redirecting the user to the Uri created by the provider module and they will be redirected to the providers website, the callback Url is the next route you need to implement.

The code used to generate the callback Url in this sample is as follows:

private Uri GetReturnUrl(string relativeUrl, string providerKey)
{
    var builder = new UriBuilder(Request.Url)
    {
        Path = relativeUrl,
        Query = "providerkey=" + providerKey.ToLowerInvariant()
    };

    // Don't include port 80/443 in the Uri.
    if (builder.Uri.IsDefaultPort)
    {
        builder.Port = -1;
    }

    return builder.Uri;
}

It is recommended that you make the callback url completely lowercase, Google is case-sensitive and if you get the casing wrong, the authentication and redirection will fail.

Creating the callback route

Using the route defined above, implement another route for /authentication/authenticatecallback

Get["/authentication/authenticatecallback"] = _ =>
{
    if (string.IsNullOrEmpty(Request.Query.providerkey))
    {
        throw new ArgumentException("No provider key was supplied on the callback.");
    }

    var existingState = (Session[StateKey] as string) ?? string.Empty;
    var model = new AuthenticateCallbackData();
    var querystringParameters = new NameValueCollection();

    foreach (var item in Request.Query)
    {
        querystringParameters.Add(item, Request.Query[item]);
    }

    try
    {
        model.AuthenticatedClient =
            authenticationService.GetAuthenticatedClient((string) Request.Query.providerKey,
                                                         querystringParameters, existingState);
    }
    catch (Exception exception)
    {
        model.Exception = exception;
    }

    return View["authenticated", model);
};

This route checks to see if a provider key was supplied in the query string, if so it grabs the state we defined in the redirect route, passing the information into the authenticationService we parse all the data from the provider and give you what you need.

After that, it's up to you to handle it, you can either authenticate with the database, create a authentication cookie, what ever your little heart desires.