Adding HTTP Basic Authentication Header to Backbone.js Sync Function Prevents Model from Being Updated on Save()
Asked Answered
M

4

5

I'm working on a web application that is powered by a restful API written with Python's CherryPy framework. I started out writing the user interface with a combination of jQuery and server side templates, but eventually switched to Backbone.js because the jQuery was getting out of hand.

Unfortunately, I'm having some problems getting my models to sync with the server. Here's a quick example from my code:

$(function() {
    var User = Backbone.Model.extend({
        defaults: {
            id: null,
            username: null,
            token: null,
            token_expires: null,
            created: null
        },

        url: function() {
            return '/api/users';
        },

        parse: function(response, options) {
            console.log(response.id);
            console.log(response.username);
            console.log(response.token);
            console.log(response.created);
            return response;
        }
    });

    var u = new User();
    u.save({'username':'asdf', 'token':'asdf'}, {
        wait: true,
        success: function(model, response) {
            console.log(model.get('id'));
            console.log(model.get('username'));
            console.log(model.get('token'));
            console.log(model.get('created'));
        }
    });
});

As you can probably tell, the idea here is to register a new user with the service. When I call u.save();, Backbone does indeed send a POST request to the server. Here are the relevant bits:

Request:

Request URL: http://localhost:8080/api/users
Request Method: POST
Request Body: {"username":"asdf","token":"asdf","id":null,"token_expires":null,"created":null}

Response:

Status Code: HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 109
Response Body: {"username": "asdf", "created": "2013-02-07T13:11:09.811507", "token": null, "id": 14, "token_expires": null}

As you can see, the server successfully processes the request and sends back an id and a value for created. But for some reason, when my code calls console.log(u.id);, I get null, and when my code calls console.log(u.created);, I get undefined.

tl;dr: Why isn't Backbone.js persisting changes to my objects after a call to save()?

Edit: I've modified the above code so that the model properties are accessed using the get function in a success callback. This should solve any concurrency problems with the original code.

I've also added some console logging in the model's parse function. Oddly enough, each of these is undefined... Does that mean that Backbone.js is failing to parse my response JSON?

Edit 2: A few days ago, I found out that issue was actually a custom header that I was adding to every request to enable HTTP Basic Authentication. See this answer for details.

Marcimarcia answered 7/2, 2013 at 13:30 Comment(1)
Your code looks fine (except that you should use Model.urlRoot instead of Model.url, but that's not the problem here). Earlier revisions of your code had problems, but are you absolutely sure that the exact code in your current edit still fails?Metabolite
M
3

I figured out the issue, although I'm still at a loss to explain why it's an issue at all. The web app that I'm building has an authentication process that requires an HTTP basic Authentication header to be passed with all requests.

In order to make this work with Backbone, I overrode the Backbone.sync function and changed line 1398 to add the header.

Original Code:

var params = {type: type, dataType: 'json'};

Modified Code:

var params = {
    type: type,
    dataType: 'json',
    headers: {
        'Authorization': getAuthHash()
    }
};

The function getAuthHash() just returns a Base64 string that represents the appropriate authentication information.

For some reason, the addition of this header makes the sync/save functions fail. If I take it out, everything works as you might expect it to.

The bounty is still open, and I'll happily reward it to anybody who can explain why this is, as well as provide a solution to the problem.

Edit:

It looks like the problem was the way that I was adding the header to the request. There's a nice little JavaScript library available on Github that solves this problem by correctly adding the HTTP Basic Auth header.

Marcimarcia answered 21/2, 2013 at 13:20 Comment(0)
M
3

This code:

u.save();
console.log(u.id);
console.log(u.username);
console.log(u.token);
console.log(u.created);

Runs immediately... after that there is nothing to run and the queued ajax request begins. The response then comes a bit later and only at that point have the values changed.

It also seems that those properties are not directly on the object, but the asynchronous processing of the save still holds in that you wouldn't get expected results even if you corrected that code to have console.log(u.get("id")) etc.

Milquetoast answered 7/2, 2013 at 13:34 Comment(4)
Your suggestion of using u.get("id") instead of u.id makes a lot of sense, but even if I try to log those properties in a success callback or on the model's sync event, they remain unset, so I think there's more to the problem.Marcimarcia
I've edited the code in the original question to incorporate your suggestions... It still isn't working, and I'm at my wits end.Marcimarcia
@Marcimarcia if you have defined a parse function then it's your responsibility to return the parsed model from the response. This function is used when the response from the server is not directly structured to create a model fromMilquetoast
@Marcimarcia you can debug it in the parse function like so console.log( JSON.parse(JSON.stringify(response)))Milquetoast
M
3

I figured out the issue, although I'm still at a loss to explain why it's an issue at all. The web app that I'm building has an authentication process that requires an HTTP basic Authentication header to be passed with all requests.

In order to make this work with Backbone, I overrode the Backbone.sync function and changed line 1398 to add the header.

Original Code:

var params = {type: type, dataType: 'json'};

Modified Code:

var params = {
    type: type,
    dataType: 'json',
    headers: {
        'Authorization': getAuthHash()
    }
};

The function getAuthHash() just returns a Base64 string that represents the appropriate authentication information.

For some reason, the addition of this header makes the sync/save functions fail. If I take it out, everything works as you might expect it to.

The bounty is still open, and I'll happily reward it to anybody who can explain why this is, as well as provide a solution to the problem.

Edit:

It looks like the problem was the way that I was adding the header to the request. There's a nice little JavaScript library available on Github that solves this problem by correctly adding the HTTP Basic Auth header.

Marcimarcia answered 21/2, 2013 at 13:20 Comment(0)
L
2

i have tested your code, its works fine for me.

See Demo here, jsfiddle.net/yxkUD/

Lacreshalacrimal answered 27/2, 2013 at 5:54 Comment(1)
Sorry, I didn't add additional detail about the problem to the question. See this description for more detailsMarcimarcia
L
1

Try to add a custom beforeSend method to the ajax request to add the custom header.

For example: https://github.com/documentcloud/backbone/blob/master/backbone.js#L1424

Lipps answered 25/2, 2013 at 14:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.