32

Some areas of my SPA need to be open to all users, and some areas require authentication. In these areas, it's the data loaded via AJAX that I want to protect.

I have an authentication service (see below), which I add as a dependency in my durandal main.js. The service is called:

authentication

In my main.js I call

authentication.handleUnauthorizedAjaxRequest(function () {
        app.showMessage('You are not authorized, please login')
        .then(function () {
            router.navigateTo('#/user/login');
        });
    });

It warns the user they are not authorized, and navigates user to a login view/viewmodel where they can enter details and try logging in.

Some questions that come to mind when building this authentication viewModel:

  • Are there any obvious concerns with what I'm doing?
  • Is this how I'm 'meant' to do things in Durandal?
  • Am I re-inventing the wheel? I couldn't see anything like this within Durandal.

Most people seem to be creating seperate cshtml pages; one for login (if the user is not authenticated), and the usual index.cshtml Is there any good reasons for me to switch to that method?

My login action on my the server-side 'user controller' has the [ValidateAntiForgeryToken] attribute I need to send that as well.
I also have an 'antiforgery' service (see below) which I also add as a dependency in my main.js viewModel file then (also in my main.js).

antiforgery.addAntiForgeryTokenToAjaxRequests();

This intercepts all ajax requests (along with content), and adds the MVC AntiForgeryToken value to the data. Seems to work exactly as I want it to. Please let me know if there's any errors/mistakes.

Complete authentication service below.

// services/authentication.js
define(function (require) {
    var system = require('durandal/system'),
    app = require('durandal/app'),
    router = require('durandal/plugins/router');

    return {
        handleUnauthorizedAjaxRequests: function (callback) {
            if (!callback) {
                return;
            }
            $(document).ajaxError(function (event, request, options) {
                if (request.status === 401) {
                    callback();
                }
            });
        },

        canLogin: function () {         
            return true;
        },
        login: function (userInfo, navigateToUrl) {
            if (!this.canLogin()) {
                return system.defer(function (dfd) {
                    dfd.reject();
                }).promise();
            }
            var jqxhr = $.post("/user/login", userInfo)
                .done(function (data) {
                    if (data.success == true) {
                        if (!!navigateToUrl) {
                            router.navigateTo(navigateToUrl);
                        } else {
                            return true;
                        }
                    } else {
                        return data;
                    }
                })
                .fail(function (data) {
                    return data;
                });

            return jqxhr;
        }
    };
});

// services/antiforgery.js
define(function (require) {
    var app = require('durandal/app');

    return {
        /*  this intercepts all ajax requests (with content)
            and adds the MVC AntiForgeryToken value to the data
            so that your controller actions with the [ValidateAntiForgeryToken] attribute won't fail

            original idea came from http://stackoverflow.com/questions/4074199/jquery-ajax-calls-and-the-html-antiforgerytoken

            to use this

            1) ensure that the following is added to your Durandal Index.cshml
            <form id="__AjaxAntiForgeryForm" action="#" method="post">
                @Html.AntiForgeryToken()
            </form>

            2) in  main.js ensure that this module is added as a dependency

            3) in main.js add the following line
            antiforgery.addAntiForgeryTokenToAjaxRequests();

        */
        addAntiForgeryTokenToAjaxRequests: function () {
            var token = $('#__AjaxAntiForgeryForm     input[name=__RequestVerificationToken]').val();
            if (!token) {
                app.showMessage('ERROR: Authentication Service could not find     __RequestVerificationToken');
            }
            var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(token);

            $(document).ajaxSend(function (event, request, options) {
                if (options.hasContent) {
                    options.data = options.data ? [options.data, tokenParam].join("&") :     tokenParam;
                }
            });
        }

    };
});
stooboo
  • 917
  • 9
  • 15
  • Hi Stuart, Your idea is great, and I'm trying to apply it in my Hot Towel/Durandal project. I got the auth and antiforgery services, but I'm not getting the auth service to intercept unauthorized users and redirect them to the login page. How do I do that? do I need to add something to Index.cshtml? Thanks – Ali B Apr 10 '13 at 01:32
  • This is also on DurandalJs Google Group - there's been updates from other people on there and I've also added some code examples - they won't really fit in a comment here ... so ;-) .. here's the link https://groups.google.com/forum/?hl=en&fromgroups=#!topic/durandaljs/iq9OPprfob0 – stooboo Apr 12 '13 at 09:53

1 Answers1

14

I prefer to pass the antiforgery token in the header. This way its easy to parse out of the request on the server because its not intermingled with your form's data.

I then created a custom action filter to check for the antiforgery token.

I created a post already on how to do this.

Community
  • 1
  • 1
Evan Larsen
  • 9,935
  • 4
  • 46
  • 60
  • Cheers Evan, I hadn't thought about putting in the headers ( I didn't see your post so thanks for the link) – stooboo Apr 06 '13 at 02:42