Roy Philips
Roy Philips

Reputation: 342

How to do 0-legged OAuth in Windows Phone 8.1

I'm porting an app from Android to Windows Phone 8.1. I need to fetch some data that needs an OAUTh authentication. I did some research and I think i need a 0-legged OAuth authentication, because i only have a key & secret and the url to get the data.

In the Android project the code is like this (JAVA):

public JSONObject fetchData() {
        try {
            OAuthConsumer consumer = new DefaultOAuthConsumer(this.context.getString(R.string.consumer_key), this.context.getString(R.string.consumer_secret));

            URL fullURL = new URL(url + "&start=" + start + "");

            HttpURLConnection request = (HttpURLConnection) fullURL.openConnection();

            consumer.sign(request);
            request.setConnectTimeout(15000);
            request.setRequestMethod("GET");
            request.setRequestProperty("Accept", "application/json");
            request.setRequestProperty("Accept-Encoding", "gzip");
            request.connect();

            switch (request.getResponseCode()) {
                case 200:
                case 201:
                    BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = br.readLine()) != null) {
                        sb.append(line + "\n");
                    }
                    br.close();

                    return new JSONObject(sb.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

My code that i have in Windows phone 8.1 project is (C#):

public async Task<List<UiTEvent>> FetchEvents(ZoekResultatenExtras zre, double locationLatitude, double locationLongitude)
{
    String completeURL;
    List<UiTEvent> events = new List<UiTEvent>();
    if (zre.CurrentLocation)
    {
        completeURL = SEARCH_EVENTS_URL + zre.SearchQuery + "&pt=" + locationLatitude + "," + locationLongitude + "";
    }
    else
    {
        completeURL = SEARCH_EVENTS_URL + zre.SearchQuery;
    }

    Debug.WriteLine(completeURL);
    using (HttpClient client = new HttpClient())
    {
            OAuthBase oauth = new OAuthBase();
            string normalizedUrl;
            string normalizedqueryparameters;
            var key = "MY_KEY";
            var secret = "MY_SECRET";
            var URL = new Uri("http://www.uitid.be/uitid/rest/searchv2/search?fq=type%3Aevent&fq=language%3Anl&group=event&rows=15&q=*%3A*&sfield=physical_gis&sort=geodist%28%29+asc&d=10&datetype=today&fq=-category_id%3A0.3.1.0.0&pt=51.0554827,3.7407583&start=0");

            var oauth_nonce = oauth.GenerateNonce();
            var oauth_timestamp = oauth.GenerateTimeStamp();
            var oauth_signature_method = "HMAC-SHA1";
            var oauth_signature = Uri.EscapeDataString(oauth.GenerateSignature(URL, key, secret, null, null, "GET", oauth_timestamp, oauth_nonce, out normalizedUrl, out normalizedqueryparameters));


            String dfd = "OAuth oauth_consumer_key=\"" + key + "\", oauth_nonce=\"" + oauth_nonce + "\", oauth_signature=\"" + oauth_signature + "\", oauth_signature_method=\"" + oauth_signature_method + "\", oauth_timestamp=\"" + oauth_timestamp + "\", oauth_version=\"1.0\"";


            client.DefaultRequestHeaders.Add("Authorization", dfd);
            client.DefaultRequestHeaders.Add("Accept", "application/json");
            client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
        using (HttpResponseMessage response = await (client.GetAsync(completeURL)))
        {
            if (response.IsSuccessStatusCode)
            {
                String content = await response.Content.ReadAsStringAsync();
            }
        }
    }
    return events;
}

I use OAuthBase.cs from https://code.google.com/p/oauth/issues/detail?id=223

When I run my project I can see in Charles that I always receive the response code "401 Unauthorized"

What am I doing wrong?

UPDATE 1:

I got the OAuthBase.cs library working! My code is:

OAuthBase o = new OAuthBase();
            var normalizedUrl = String.Empty;
            var normalizedParameters = String.Empty;
            var oauth_consumer_key = "MY KEY";
            var oauth_consumer_secret = "MY SECRET";
            var oauth_timestamp = o.GenerateTimeStamp();
            var oauth_nonce = o.GenerateNonce();
            var oauth_signature_method = "HMAC-SHA1";
            var oauth_signature = o.GenerateSignature(new Uri(completeURL), oauth_consumer_key, oauth_consumer_secret, null, null, "GET", oauth_timestamp, oauth_nonce, out normalizedUrl, out normalizedParameters);
            var oauth = "OAuth oauth_consumer_key=\"" + oauth_consumer_key + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_signature=\"" + Uri.EscapeDataString(oauth_signature) +"\",oauth_signature_method=\"" + oauth_signature_method + "\",oauth_timestamp=\"" + oauth_timestamp + "\",oauth_version=\"1.0\"";
            var basestring = o.GenerateSignatureBase(new Uri(completeURL), oauth_consumer_key, null, null, "GET", oauth_timestamp, oauth_nonce, oauth_signature_method, out normalizedUrl, out normalizedParameters);

            Debug.WriteLine(completeURL);
            Debug.WriteLine(basestring);

            using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Add("Authorization", oauth);
                client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json");
                client.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Encoding", "gzip");

                using (HttpResponseMessage response = await (client.GetAsync(completeURL)))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        String content = await response.Content.ReadAsStringAsync();
                        Debug.WriteLine(content);
                    }
                }
            }

But I still got a problem on some URL's where I want to fetch data. I found this thanks to a handy website:

I've noticed that sometimes my basestring decoding is wrong and that's why it give a 401 Unauthorized error.

Good basestring:

GET&http%3A%2F%2Fwww.uitid.be%2Fuitid%2Frest%2Fsearchv2%2Fsearch&fq%3Dlanguage%253Anl%26fq%3Dtype%253Aevent%26group%3Devent%26oauth_consumer_key%3Def08c84b91c842bbf4f182d188dd4e57%26oauth_nonce%3DNjM1NDY3MTE0NzI3NTM3MDIw%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1411107473%26oauth_version%3D1.0%26q%3D%252A%253A%252A%26rows%3D15

My basestring:

GET&http%3A%2F%2Fwww.uitid.be%2Fuitid%2Frest%2Fsearchv2%2Fsearch&fq%3Dlanguage%253Anl%26fq%3Dtype%253Aevent%26group%3Devent%26oauth_consumer_key%3Def08c84b91c842bbf4f182d188dd4e57%26oauth_nonce%3DNjM1NDY3MTE0NzI3NTM3MDIw%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1411107473%26oauth_version%3D1.0%26q%3D%2A%253A%2A%26rows%3D15

I got * characters in my URL that doesn't got encoded like OAuth wants it.

For my decoding I use the method:

private static String BuildQueryString(List<StringKeyValue> parameters)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }

            StringBuilder builder = new StringBuilder();
            foreach (var pair in parameters.Where(p => !String.IsNullOrEmpty(p.Value)))
            {
                if (builder.Length > 0)
                {
                    builder.Append("&");
                }

                builder.Append(Uri.EscapeDataString(pair.Key));
                builder.Append("=");
                builder.Append(Uri.EscapeDataString(pair.Value));
            }
            return builder.ToString();
        }

I think i need to make use of the RFC3986 encoding.. but I don't know how to implement it. I found this relevant question: How to get Uri.EscapeDataString to comply with RFC 3986

but the Uri.HexEscape method doesn't work in WP8.1

Upvotes: 2

Views: 665

Answers (1)

Roy Philips
Roy Philips

Reputation: 342

I found the solution for my own problem.

Find a OAuth library compatible with WP8.1:

First u need a working library that is compatible with Windows Phone 8.1. In my case I used the class OAuthBase.cs from https://code.google.com/p/oauth/issues/detail?id=223

Encode your URL & parameters so it make use of RFC3986 (OAuth makes use of that)

I had a lot of problems with OAuth (401 Unauthorized error when fetching data) because I encoded my URL with the method Uri.EscapeDataString() but that had problem with reserved characters like * ( ) ! ' [ ] ,

So I searched for a solution for that and found this: How to get Uri.EscapeDataString to comply with RFC 3986. The only problem was that the Uri.HexEscape function doesn't work anymore in WP8.1 so I changed the method a bit.

The method can be found here:

private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ",", ")", "[","]" };

        internal static string EscapeUriDataStringRfc3986(string value)
        {
            StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));
            for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
            {
                switch (UriRfc3986CharsToEscape[i])
                {                  
                    case "!":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%21");
                        break;
                    case "*":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%2A");
                        break;
                    case "'":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%27");
                        break;
                    case "(":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%28");
                        break;
                    case ",":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%2C");
                        break;
                    case ")":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%29");
                        break;
                    case "[":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%5B");
                        break;
                    case "]":
                        escaped.Replace(UriRfc3986CharsToEscape[i], "%5D");
                        break;
                }            
            }
            return escaped.ToString();
        }

Build the OAuth header and sign it to the HttpClient

For building the OAuth header u can use some methods from OAuthBase.cs:

OAuthBase o = new OAuthBase();
            var normalizedUrl = String.Empty;
            var normalizedParameters = String.Empty;
            var oauth_consumer_key = "MY KEY";
            var oauth_consumer_secret = "MY SECRET";
            var oauth_timestamp = o.GenerateTimeStamp();
            var oauth_nonce = o.GenerateNonce();
            var oauth_signature_method = "HMAC-SHA1";
            var oauth_signature = o.GenerateSignature(new Uri(completeURL), oauth_consumer_key, oauth_consumer_secret, null, null, "GET", oauth_timestamp, oauth_nonce, out normalizedUrl, out normalizedParameters);
            var oauth = "OAuth oauth_consumer_key=\"" + oauth_consumer_key + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_signature=\"" + Uri.EscapeDataString(oauth_signature) +"\",oauth_signature_method=\"" + oauth_signature_method + "\",oauth_timestamp=\"" + oauth_timestamp + "\",oauth_version=\"1.0\"";

After building the OAuth header string you only need to add it to the HttpClient:

HttpClient client = new HttpClient()
client.DefaultRequestHeaders.Add("Authorization", oauth);

The complete code:

String completeURL = EscapeUriDataStringRfc3986("http://www.myurl.be/*!(),");
        OAuthBase o = new OAuthBase();
        var normalizedUrl = String.Empty;
        var normalizedParameters = String.Empty;
        var oauth_consumer_key = "MY KEY";
        var oauth_consumer_secret = "MY SECRET";
        var oauth_timestamp = o.GenerateTimeStamp();
        var oauth_nonce = o.GenerateNonce();
        var oauth_signature_method = "HMAC-SHA1";
        var oauth_signature = o.GenerateSignature(new Uri(completeURL), oauth_consumer_key, oauth_consumer_secret, null, null, "GET", oauth_timestamp, oauth_nonce, out normalizedUrl, out normalizedParameters);
        var oauth = "OAuth oauth_consumer_key=\"" + oauth_consumer_key + "\",oauth_nonce=\"" + oauth_nonce + "\",oauth_signature=\"" + Uri.EscapeDataString(oauth_signature) +"\",oauth_signature_method=\"" + oauth_signature_method + "\",oauth_timestamp=\"" + oauth_timestamp + "\",oauth_version=\"1.0\"";

        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("Authorization", oauth);
            client.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/json");
            client.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Encoding", "gzip");

            using (HttpResponseMessage response = await (client.GetAsync(completeURL)))
            {
                if (response.IsSuccessStatusCode)
                {
                    String content = await response.Content.ReadAsStringAsync();
                    //DO SOMETHING WITH CONTENT                 
                }
            }
        }

Upvotes: 2

Related Questions