Как запросить JSONP файл через jQuery AJAX из Upwork API, который использует аутентификацию OAuth 1.0?

Мне нужно запросить JSONP файл из Upwork API через jQuery AJAX. API Upwork API использует аутентификацию OAuth 1.0.

Я новичок в Oauth, но читал об этом за последние несколько дней, и я вообще понимаю, как это работает, но было очень сложно реализовать его в этом конкретном сценарии/среде. Я бил головой в течение нескольких дней, и поддержка API Upwork API не помогла: (

Мне нужно пройти все необходимые шаги в OAuth 1.0 и получить параметры OAuth, переданные с URL-адресом запроса. Пожалуйста, помогите!

Вот что я сделал до сих пор:

// My Upwork API key and secret
var api_key = 'xxx',
    api_secret = 'xxx';


// TO-DO
// OAuth 1.0 authentication


// TO-DO
// required oauth parameters
// https://developers.upwork.com/?lang=node#authentication_required-oauth-10-parameters
var oauth_consumer_key = '',
    oauth_signature = '',
    oauth_nonce = '',
    oauth_signature_method = '',
    oauth_timestamp = '',
    oauth_token = '';


// Compose request url with required oauth parameters
var url  = "https://www.upwork.com/api/profiles/v2/search/jobs.json?q=java&callback=?";
url += "&oauth_consumer_key="+oauth_consumer_key;
url += "&oauth_signature="+oauth_signature;
url += "&oauth_nonce="+oauth_nonce;
url += "&oauth_signature_method="+oauth_signature_method;
url += "&oauth_timestamp="+oauth_timestamp;
url += "&oauth_token="+oauth_token;


// Ajax request
// https://developers.upwork.com/?lang=node#getting-started_cross-domain-requests
$.ajax({
  url: url,
  dataType: 'JSONP',
  success:function(json){
    alert("Success: "+json.server_time);
  },
  error:function(){
    alert("Error");
  },
});

CodePen: http://codepen.io/nunoarruda/pen/xZBEzB?editors=1010

Спасибо заранее!

Ответ 1

TL;DR Я начинаю с описания процесса OAuth 1.0, чтобы убедиться, что примеры кода ниже и мои выводы будут ясными. Перейдите к части The code, если процесс OAuth прозрачен.

Процесс OAuth 1.0

Я использую следующие термины ниже (они отличаются от официальной терминологии, но, надеюсь, сделают все более ясным):

  • Приложение - ваше приложение
  • Сервис - услуга, запрашиваемая у
  • Пользователь - пользователь, который дает вам доступ к своим данным, хранящимся в Сервисе.

Приготовление. Зарегистрируйте свое приложение в службе

Вы получите ключ клиента и секрет, используемый для запуска процесса Oauth.

В случае Upwork вы делаете это здесь - https://www.upwork.com/services/api/apply.

Шаг 1. Получите временный токен oauth.

Этот запрос создается вашим Приложением в Службе. Ваше приложение передает client key, поэтому служба знает, кто спрашивает.

Запрос подписан с помощью client secret, Служба также имеет его и может проверить, действительно ли это запрос от вашего приложения, а не от кого-то другого, который украл ваш клиентский ключ (вот почему вы не должны покажите свой секрет кому-либо).

Сервер возвращает temporary oauth token + temporary oauth secret.

В случае Upwork вы отправляете этот запрос https://www.upwork.com/api/auth/v1/oauth/token/request

Шаг 2. Попросите пользователя предоставить вам доступ.

Ваше приложение просто перенаправляет пользователя на специальный URL-адрес, предоставляемый службой.

Служба показывает диалог, в котором пользователь может предоставить доступ для вашего приложения. Этот специальный URL-адрес включает в себя temporary token с шага 1, поэтому служба знает, какое приложение запрашивает доступ.

Если у вас есть веб-приложение, вы просто открываете этот специальный URL-адрес в браузере. Затем Служба перенаправляет обратно в ваше приложение, используя oauth_callback (URL-адрес для перенаправления пользователя). Служба также передает oauth_verifier на oauth_callback URL.

Если у вас есть настольное приложение, оно должно запуститься в браузере, и служба может показать oauth_verifier как строку, чтобы пользователь мог вручную скопировать ее и вставить обратно в приложение. В этом случае вы установите oauth_calback специальное значение oob (вне диапазона). Эта часть (без перенаправления назад) строго не описана в спецификации, поэтому детали зависят от Сервиса. Он может вообще не поддерживаться или поддерживаться каким-либо другим способом.

В случае Upwork вы отправляете пользователя в URL https://www.upwork.com/services/api/auth?oauth_token= {временный токен}

Шаг 3. Получите реальный токен доступа oauth.

Ваше приложение отправляет временный токен из шага 1 и oauth verifier с шага 2 в службу. Запрос снова подписан, но на этот раз с помощью client secret и temporary token secret. Служба отвечает ключом токена доступа +.

В случае Upwork URL-адрес https://www.upwork.com/api/auth/v1/oauth/token/access

Это 3 шага, чтобы получить реальный доступ к ним и начать использовать Service API. Пример в спецификации также хорош и ясен, проверить его.

Также обратите внимание, что OAuth 1.0 нельзя безопасно использовать в 100% клиентских приложениях. На шаге 1 вам нужно использовать частный client secret, который не должен быть известен никому (поэтому вы не должны его размещать в своем клиентском коде). На шаге 2 Служба перенаправит браузер обратно на oauth_callback, и вы не сможете обработать его на стороне клиента.

Технически можно использовать oauth на стороне клиента, если вы используете сценарий без обратного вызова, как для настольного приложения. В этом случае пользователю необходимо вручную скопировать верификатор обратно в ваше приложение. Этот сценарий также должен поддерживаться Servcie (Upwork не поддерживает его, см. Ниже).

Шаг 4. Используйте Service API

Теперь, как только вы получите токен доступа, вы можете сделать запросы API для получения данных, здесь вы отправляете оба client key и access token из шага 3. Запросы подписаны с помощью client secret + access token secret.

Самая сложная часть процесса - это подписание запросов, это подробно описано в спецификации, но здесь лучше использовать библиотеку.

oauth-1.0a позволяет подписать ваши запросы в node.js и в javascript на стороне клиента. Вам все равно нужно выполнить шаги oauth из вашего приложения, библиотека поможет вам подписаться.

Код

Я протестировал Step 1 из браузера javascript, и Upwork не поддерживает этот сценарий. Если я отправляю обычный запрос POST с помощью ajax, он возвращает "Access-Control-Allow-Origin error. And if I try this request using JSONP`, Upwork отвечает ошибкой 404.

Таким образом, для конечной точки api/auth/v1/oauth/token/request нет поддержки JSONP.

Этапы 1-3 должны выполняться с использованием серверной части (любая аутентификация на стороне клиента будет небезопасной).

Вот как выглядит запрос маркера (Step 1):

oauthTest.step1_tempToken = function() {
    var request_data = {
        url: 'https://www.upwork.com/api/auth/v1/oauth/token/request',
        method: 'POST',
        data: {}
    };
    request({
        url: request_data.url,
        method: request_data.method,
        form: oauthTest.oauth.authorize(request_data) // no token yet
    }, function(error, response, body) {
        var data = qs.parse(body);
        console.log(data);
    });
};

Полный код здесь.

Обратите внимание, что Upwork имеет libraryjs library, но я не использовал его, чтобы делать все вручную. Запросы подписываются с помощью oauth-1.0a.

В браузере выполняется Step 2, здесь вы просто открываете url как https://www.upwork.com/services/api/auth?oauth_token=xxx 'и получаете верификатор oauth. В сценарии реальной жизни ваше приложение будет указывать параметр oauth_callback, а Upwork будет отправлять верификатор oauth в ваше приложение. В этом примере я просто вручную скопирую его из браузера и перейду к следующему шагу.

Имея верификатор oauth, вы можете получить токен постоянного доступа (Step 3):

oauthTest.step3_accessToken = function(oauth_verifier) {
    var request_data = {
        url: 'https://www.upwork.com/api/auth/v1/oauth/token/access',
        method: 'POST',
        data: {
          oauth_verifier: oauth_verifier
        }
    };
    request({
        url: request_data.url,
        method: request_data.method,
        form: oauthTest.oauth.authorize(request_data, oauthTest.tempToken) // use the temp token
    }, function(error, response, body) {
        var data = qs.parse(body);
        console.log(data);
    });
};

Наконец, вы можете использовать API, Step 4 (опять же, это код на стороне сервера):

oauthTest.queryAPI = function() {
    var request_data = {
        url: 'https://www.upwork.com/api/profiles/v2/search/jobs.json',
        method: 'GET',
        data: {
          'q': 'java'
        }
    };
    request({
        url: request_data.url,
        method: request_data.method,
        qs: oauthTest.oauth.authorize(request_data, oauthTest.accessToken) // use the access token
    }, function(error, response, body) {
        console.log(body);
    });
};

Можно использовать API с клиентской стороны (хотя это не хорошо, потому что вам нужно поместить свой токен доступа и секрет в код).

Решение сложное, потому что документация (https://developers.upwork.com/?lang=node#getting-started_cross-domain-requests) не является полной и не совсем правильной.

В нем говорится добавить callback=? к запросу, но jQuery автоматически добавляет этот параметр, когда вы устанавливаете тип данных JSONP. Также значение параметра установлено на некоторую случайную строку, поэтому я думал, что этот параметр не должен быть подписан, но кажется, что он должен:

function queryAPI(public, secret) {
    var accessToken = {
        public: public,
        secret: secret
    }
    var request_data = {
        url: 'https://www.upwork.com/api/profiles/v2/search/jobs.json',
        method: 'GET',
        data: {
          'q': 'java',
          'callback': 'jsoncallback'
        }
    };

    // It looks like a bug on the Upwork side, the `callback` parameter is usually
    // selected randomly by jQuery, so server side should skip it from the signature
    // validation, but it doesn't, so we sign the request with `callback` parameter
    // and then remove it from data, because it this parameter is automatically added
    // by jQuery, we also set the static value for callback - 'jsoncallback`
    var data = oauth.authorize(request_data, accessToken);
    delete data.callback;

    // Ajax request
    // https://developers.upwork.com/?lang=node#getting-started_cross-domain-requests
    $.ajax({
      // url: url,
      url: request_data.url,
      dataType: 'JSONP',
      jsonpCallback: 'jsoncallback',
      // here the data will contain 'q=java' as well as all the oauth parameters
      // the request type will be GET (since this is JSONP), so all parameters will
      // be converted to the query string
      // you can check the URL in the developer console, in the list of network requests
      //data: oauth.authorize(request_data, accessToken),
      data: data,
      cache: true, // this removes the '_' parameter
      success:function(json){
        console.log(json);
      },
      error: function(error){
        console.log(error);
      },
    });
};

В любом случае это небезопасно, и поскольку вам нужна серверная сторона для Oauth, вы также можете использовать его, чтобы сделать запросы API к API и вернуть результаты на стороне клиента.

Как использовать пример кода

Получите копию папки nodejs-upwork-oauth, выполните npm install и запустите консоль node.js:

$ node
> oauthTest = require('./server')
> oauthTest.step1_tempToken()
> // wait for the result
{ public: 'xxxx',
  secret: 'yyyy' }
> // copy the public temp access token
> // don't exit it yet
>

Теперь откройте test.html в браузере и откройте консоль JS, запустите:

> step2_askUser('temp_access_token_here')
> // it will open the upwork auth page in new tab
Application authorized

jobs-alert has been authorized.
Your oauth_verifier=zzzz

You can close this window and return to your application.
> // authorize there and copy the oauth_verifier

Вернитесь к консоли nodejs:

> oauthTest.step3_accessToken('oauth verifier here')
> // wait for the result
{ public: 'nnnnn',
  secret: 'kkkkk' }
> oauthTest.queryAPI()
> // see the query result

И вернитесь в браузер:

> queryAPI('access token public', 'access token secret')
< Object {server_time: 1456301893, auth_user: Object, profile_access: "public,odesk", jobs: Array[10], paging: Object}