http-redirectbackbone.jslogout

How to handle server's request for logout confirmation in Backbone


I've got a project comprising a servlet/restlet back end (written in a Java8 framework) and Backbone front end.

The Backbone has a "Logout" button to terminate the user's browser session with the server and return them to the application's login prompt.

The original version of the Java framework included a web server which did not make a request to confirm the logout. As such, the Backbone code works just fine. Chrome DevTools shows the network call log as follows:

enter image description here

The Java framework now has to be updated to a much more recent version; the Backbone code hasn't been modified. The web service in the newer Java framework is now requesting a logout confirmation which the Backbone isn't handling as needed: instead of being re-directed to the login prompt, the current page of the application (the one containing the Logout button) is simply re-displayed, so the only way to effect a logout is to close the browser window and restart the back end ! Chrome DevTools shows this new network call log as follows (please ignore the first line relating to panels):

enter image description here

I'm new to Backbone and didn't write the original code. I'm simply trying to work out how to change it to get the logout working again by responding appropriately to the logoutConfirm. I've taken the original code, without third-party libraries, then stripped out anything that is purely application related to leave only the code relating to authorisation and logout -- I hope that's OK as a simplified reproduction here of the code I'm working with (see below).

Incidentally, the event corresponding to the click of the Logout button is the 'click .logoutlink' shown almost at the end of the code sample.

Can anyone explain to me what needs changing and why ? Many thanks.

function AppAuth() {
    return {
        _currentUser: undefined,
        _redirectTo: undefined,

        isAuthorized: function () {
            return app.cookieCutter.get('apphash') !== undefined;
        },

        currentUser: function () {
            return this._currentUser;
        },

        // If user authorised, just return. If not, change the hash to /login
        authorizedOrRedirect: function () {
            if (!app.auth.isAuthorized() && window.location.hash != '/login') {
                app.auth._redirectTo = window.location.hash; // Save current page to go back to
                this.logout();
                return false;
            }
        },

        logout: function () {
            if (this._currentUser != undefined) {
                this._currentUser.trigger('logout');
            }

            this._currentUser = undefined;

            $.ajax("/logout", {
                type: "GET"
            });

            var cookies = app.cookies.keys();
            _.forEach(cookies, function (cookie) {
                app.cookieCutter.remove(cookie);
            });

            window.location.pathname = "/logout";
            return true;
        },
    };
}

app = {};
app.traceUrlEvents = true;

// Comment out if NOT CORS requests. In our case they are until release:
app.apiIsCrossOrigin = true;
jQuery.support.cors = true;

// Remote working
app.apiRoot = '/v1';
app.loginEndpoint = '/login';

// Localhost
app.auth = APPAuth();

if (app._queueSameGetRequests) {
    Backbone.xhrs = {};
    var oldFetch = Backbone.Model.prototype.fetch;
    Backbone.Model.prototype.fetch = function (opts) {
        var self = this;
        var args = arguments;
        // cache GET requests, not the others
        if (!opts || opts['cache'] !== true) {
            return oldFetch.apply(this, arguments);
        }

        var promise;
        var r;

        if (_.isFunction(this.url)) {
            r = this.url();
        } else {
            r = this.url;
        }

        // issue the request if a cached version does not exist
        if (!Backbone.xhrs[r]) {
            promise = Backbone.xhrs[r] = oldFetch.apply(this, arguments);
        } else {
            var p = Backbone.xhrs[r].done(function () {
                oldFetch.apply(self, args);
            });
            return p;
        }
        return promise;
    };
}

// Default error handlers for network and other XHR errors
app.networkAuthErrorHandler = function () {
    app.auth.logout();
    // Refresh the window, this should cause redirection to login screen. Could loop ?
    console.log("302 from API, authorization required");
    app.auth.authorizedOrRedirect();
};

app.networkErrorHandler = function () {
    $('#global-warn').show();
};

// Handle transport errors/network being down
$(document).ajaxError(function (evt, resp, request, txt) {
    if (resp.status === 0) {
        app.networkErrorHandler();
    }
});

app.Router = {};

app.boot = function () {
    app.EventObject = {};
    _.extend(app.EventObject, Backbone.Events);
    new app.controllers.DefaultController();
    app.Router = new app.controllers.Auth();
    new app.controllers.Users();

    // Force authorisation check when using navigate from within app
    var triggerNewRoute = function () {
        console.log("Route change", arguments);
        app.EventObject.trigger('routeChange');
        app.auth.authorizedOrRedirect();
    };

    Backbone.$(window).on('hashchange', triggerNewRoute);

    if (app.traceUrlEvents) {
        Backbone.$(window).on('pushstate', urlTraces);
        Backbone.$(window).on('popstate', urlTraces);
        Backbone.$(window).on('hashchange', urlTraces);
    }
};

app.models.BaseModel = Backbone.Model.extend({
    apiRoot: function () {
        return app.apiRoot;
    },

    // Overwrite to support trailing slashes
    url: function () {
        var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || this.urlError();
        if (this.isNew()) return base;
        return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id) + '/';
    },
});

(function (global) {
    'use strict';
})(this);

app.controllers.Auth = app.controllers.BaseController.extend({
    routes: {
        "auth/": "getAuthPage"
    },

    getAuthPage: function () {
        var loginView = new app.views.LoginView();
        app.rootView.renderMainView(loginView, 'login');

        if (app.auth.isAuthorized()) {
            app.Router.navigate('//', { trigger: true });
        }
    }
});

// Router/controller for the user list page
app.controllers.Users = app.controllers.BaseController.extend({
    "routes": {
        "logout/": "logoutCurrentUser"
    },

    logoutCurrentUser: function () {
        console.log(">>>>> logoutCurrentUser() is empty function !!")
    }
});

// Render the title bar
app.views.Headerbar = Backbone.View.extend({
    template: 'topbar',
    className: 'navbar navbar-main',
    events: {
        'click .logoutlink': 'doLogout',
    },

    doLogout: function (e) {
        e.preventDefault();
        app.auth.logout();
        window.location = app.loginEndpoint;
        return false;
    }
});

Solution

  • The app.auth.logout() function is called when the user clicks on the logout link. This function triggers the 'logout' event on the current user object (if it exists), sets the _currentUser variable to undefined, and performs an AJAX GET request to "/logout" to notify the server about the logout action.

    After the logout function is executed, the app.auth.logoutCurrentUser() function is called. However, in the provided code, this function is empty and does not contain any specific logic. It seems to be a placeholder function that can be further implemented with the desired server-side logout confirmation logic.

    To handle the server's request for logout confirmation in Backbone, you would need to perform the following steps:

    Implement a server-side endpoint or API that handles the logout request from the client. This endpoint should perform the necessary operations to confirm the logout and invalidate the user's session.

    Modify the logoutCurrentUser function in the app.controllers.Users controller to send a request to the server's logout endpoint. You can use the $.ajax function or any other method supported by Backbone to make the request. For example:

    logoutCurrentUser: function() {
    $.ajax({
        url: "/logout/confirmation", // Replace with the appropriate server-side endpoint
        type: "POST", // or "GET" depending on your server implementation
        success: function(response) {
            // Handle the server's confirmation response here
            console.log("Logout confirmed:", response);
            // Perform any additional client-side actions if needed
        },
        error: function(xhr, status, error) {
            // Handle the error case if the server request fails
            console.log("Logout confirmation request failed:", error);
            // Perform any error handling or display appropriate messages
        }
    });
    

    }

    Customize the server-side logout endpoint to handle the logout confirmation logic according to your application's requirements. This may involve invalidating the user's session, updating the server-side state, and returning a confirmation response to the client.