7

I'm trying to get a basic API call to work however the API provider does not offer any developer support. They require that each call send in a base64 basic authentication header. When I use their online API testing utility my keys are valid but I can not get it to work within my own code or in a JSFiddle.net. I even tried to copy the "Authentication: Basic ...." value that is returned from the testing utility within my code and that did not work.

I have looked at numerous examples within Stackoverflow and I use similar code for other APIs with no problem. The base64 basic auth is new to me and when I run the code in IE the debugger states a syntax error which I can't find. I was expecting a 401 error if any error was to occur.

Any thoughts on the error or additional ways to debug it?

$(document).ready(function() {
    var mySecret = "somevalue";
    var myClientId = "somevalue";
    $.ajax({
        url: "https://www.udemy.com/api-2.0/courses/",
        type: 'GET', 
        dataType: "json", 
        contentType: "text/plain",
        beforeSend: function (xhr) {
            xhr.setRequestHeader("Authorization", "Basic " + btoa(myClientId+":"+ mySecret));
        },
        success: function(data) {
            alert("hi");
        }
    }); 
});
bhspencer
  • 13,086
  • 5
  • 35
  • 44
  • You say that you are getting a states a syntax error. Can you tell us what the error message says. – bhspencer Jul 10 '15 at 20:10
  • I don't see any details other than the message syntax error in the breakpoint with ie 10 dev console. – BlackBerry Special Jul 10 '15 at 20:12
  • 1
    Are you sure you need to add the auth header? I am able to access the url without an auth header – bhspencer Jul 10 '15 at 20:13
  • I'd try removing your `contentType: "text/plain"` as it shouldn't be needed and is probably triggering a [preflight](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests). – Dave Jul 10 '15 at 20:16
  • The api guide states that each request must have the tokens set as base64 in the header. I can also get the .json in the browser. ....hmmmm – BlackBerry Special Jul 10 '15 at 20:46
  • I created the fiddle http://jsfiddle.net/buycanadianbooks/8gqfL1ro/2/ with a basic ajax call and no authentication and it still will not return any data yet entering the API uri directly in the browser returns the json data without any authentication. My code works for other APIs and I'm stumped. I've put up 50 points since I'm stumped for a week! – BlackBerry Special Jul 13 '15 at 20:15
  • @BlackBerrySpecial are you saying this has worked with other Udemy APIS, or just other APIS in general? It looks like what you'll have to do is write a proxy server to request the Udemy API and then feed that to your application via the same domain as your application or by setting the appropriate CORS headers. – Breedly Jul 15 '15 at 13:38
  • Hi, I meant other vendor apis in general. Can you point me to a reference or example of following your proxy approach? – BlackBerry Special Jul 15 '15 at 15:11

4 Answers4

5

What you've probably got here is a problem with CORS or Cross Origin Resource Sharing. It prevents sites from arbitrarily making requests to willy nilly. The reasons for it are complicated, but it keeps you safe! Although, it's a major pain.

Fire up your handy dandy Chrome Dev Tools and take a look at what's actually happening to that network request. It definitely doesn't work in JSFiddle because of the same-origin policy (CORs).

You can get the JSON in the Browser because that's not a Cross Site Request. Look at which headers are being sent back and forth, and see if there's a way in your Udemy API portal to set an acceptable Origin for the Allow-Origin header. Your IP address probably if you're working locally.

Community
  • 1
  • 1
Breedly
  • 12,838
  • 13
  • 59
  • 83
  • But if you add additional options to the ajax call such as setting the type to plain text doesn't that handle CORS? I did that for other roduct api calls to resolve the CORS issue. – BlackBerry Special Jul 14 '15 at 15:15
  • It depends on whether or not the source you are requesting data from has an `Allow-Origin: *` header set. If not then you can't make an XHR, but you could do it on the server side. Or it could be that the server you're requesting from will only respond to a `Content-Type: application/json`. – Breedly Jul 14 '15 at 15:19
1

You can encode like this:

var header = {"Authorization": "Basic " + btoa(username + ":" + password)};

Add the header to the ajax call like this:

$.ajax({
    url: "https://www.udemy.com/api-2.0/courses/",
    type: 'GET', 
    dataType: "json", 
    contentType: "text/plain",
    header,
    success: function(data) {
        alert("hi");
    }
}); 

This works for me anyways. You'll need CORS too as suggested.

Martin Verner
  • 602
  • 7
  • 11
1

It seems that you need to set the request header with the base64 auth data manually.

The instructions are here: http://coderseye.com/2007/how-to-do-http-basic-auth-in-ajax.html

You first need to take the following code from here: http://www.webtoolkit.info/javascript-base64.html to do the base64 encoding

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/

var Base64 = {

    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    encode : function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

        }

        return output;
    },

    // public method for decoding
    decode : function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {

            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }

        }

        output = Base64._utf8_decode(output);

        return output;

    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        }

        return string;
    }

}

Then you'll need the code to construct the auth data, it's just a base64 of the user and password:

function make_base_auth(user, password) {
  var tok = user + ':' + password;
  var hash = Base64.encode(tok);
  return "Basic " + hash;
}

And then you just add the header with jQuery before making the request:

var service_url = "https://www.udemy.com/api-2.0/courses/"
$.ajax({
    type: "GET",
    url: service_url,
    dataType: "xml",
    data: "ParamId=" + FormId.value,
    processData: false,
    beforeSend : function(req) {
         req.setRequestHeader('Authorization', 
               make_base_auth ('myClientId', 'mySecret'));
    },
    error: function(XMLHttpRequest, textStatus, errorThrown) {   
         ajaxError(XMLHttpRequest, textStatus, errorThrown); 
    },
    success: function(xml) { DoSomething(xml); }
});
Bhavin Solanki
  • 4,740
  • 3
  • 26
  • 46
-1

Normally Ajax work with same domain. For achieving cross domain try with CORS CORS URL

Vijay Porwal
  • 152
  • 1
  • 8