Как подписать подпись oauth для токена запроса с помощью etrade api?

#node.js #oauth #crypt #etrade-api

#node.js #oauth #криптография #etrade-api

Вопрос:

Я пытаюсь подключиться к API eTrade для проекта. Они используют поток OAuth v1. Я сталкиваюсь с ошибкой на самом первом шаге потока — получении токена запроса. Информацию из их документов можно найти здесь.

Ошибка, которую я получаю, 401 — неверная подпись.

Я здесь прочитал о потоке OAuth и ознакомился с процессом. В прошлом я использовал PassportJS для сторонних интеграций.

Итак, eTrade предоставила мне 4 ключа — PROD_KEY, PROD_SECRET, SANDBOX_KEY, SANDBOX_SECRET. На данный момент я использую ключи изолированной среды, поскольку мой рабочий ключ все еще находится на рассмотрении.

Теперь у меня есть конечная точка в моем API, которую я вызову от своего клиента, чтобы получить токены запроса. Это структура, подобная этой:

 router.route('/auth/request').get(controller.request);
  

И на следующем контроллере я генерирую подпись и пытаюсь запросить токен у eTrade. Этот контроллер выглядит следующим образом:

 // controllers/auth.js

const axios = require('axios');
const { v1 } = require('uuid');
const crypto = require('crypto');

function generateSignature() {
  const method = 'GET',
        url = 'http://localhost/',
        encodedUrl = encodeURIComponent(url),
        paramaters = {
          oauth_consumer_key: process.env.ETRADE_SANDBOX_API_KEY,
          oauth_signature_method: 'HMAC-SHA1',
          oauth_timestamp: Math.floor(Date.now() / 1000),
          oauth_nonce: v1(),
          oauth_callback: 'oob'
        }

  var ordered = {};
  Object.keys(paramaters).sort().forEach((key) => {
    ordered[key] = paramaters[key];
  });

  var encodedParameters = '';

  for(k in ordered) {
    const encodedValue = escape(ordered[k]);
    const encodedKey = encodeURIComponent(k);

    if(encodedParameters === '') {
      encodedParameters  = encodeURIComponent(`${encodedKey}=${encodedValue}`);
    } else {
      encodedParameters  = encodeURIComponent(`amp;${encodedKey}=${encodedValue}`);
    }
  }

  encodedParameters = encodeURIComponent(encodedParameters);

  const signature_base_string = `${method}amp;${encodedUrl}amp;${encodedParameters}`;
  const signing_key = `${process.env.ETRADE_SANDBOX_SECRET_KEY}`;
  const signature = crypto.createHmac("SHA1", signing_key).update(signature_base_string).digest().toString('base64');
  const encodedSignature = encodeURIComponent(signature);

  return encodedSignature;
}

module.exports = {
  request: async (req, res) => {
    console.log('Fetching request token from etrade...');

    try {
      var response = await axios.get(`https://api.etrade.com/oauth/request_token`, {
        params: {
          oauth_consumer_key: process.env.ETRADE_SANDBOX_API_KEY,
          oauth_signature_method: 'HMAC-SHA1',
          oauth_signature: generateSignature(),
          oauth_timestamp: Math.floor(Date.now() / 1000),
          oauth_nonce: v1(),
          oauth_callback: 'oob'
        }
      });

      console.log('Fetched request token...', response.data);
    } catch (error) {
      console.log('Could not fetch request token...', error.response.data);
    }
  }
}
  

Я следовал некоторым руководствам в Интернете, чтобы помочь мне понять процесс подписания запроса, но, похоже, я не могу получить действительный ответ.

Ответ №1:

Ваш код нацелен на рабочий URL etrade. Попробуйте вызвать URL-адрес песочницы.

 Production  https://api.etrade.com/v1/{module}/{endpoint}
Sandbox     https://apisb.etrade.com/v1/{module}/{endpoint}
  

Комментарии:

1. они не определяют URL-адрес изолированной среды для конечной точки request_token, как они делают с другими конечными точками. Я пытался apisb.etrade.com/oauth/request_token и получил ту же ошибку. Я попробую еще раз, хотя

Ответ №2:

Я не знаю, ищете ли вы все еще ответ на этот вопрос, но я думаю, что следующее может помочь. Я столкнулся с аналогичной проблемой, когда пытался интегрироваться с их API (хотя я использую Java). Изучив их пример кода, я понял, что ключи для подписи базовой строки требуют дополнительного ‘amp;’ в конце для request_token API. Смотрите следующий код из их примера приложения —

        if( token != null){
            // not applicable for request_token API call
             key = StringUtils.isEmpty(token.getOauth_token_secret()) ? context.getResouces().getSharedSecret()  "amp;" : 
                 context.getResouces().getSharedSecret() "amp;"   OAuth1Template.encode(token.getOauth_token_secret());
        }else{
            // applicable for request_token API call
            key =  context.getResouces().getSharedSecret()  "amp;";
        }
  

AFAIK, это не стандарт OAuth или что-то еще. Похоже, это очень специфично для их реализации. В любом случае, я обновил свой код, включив дополнительные ‘amp;’ в конце, и он начал работать.

   private String signBaseString(String httpMethod, String url) {
    StringBuilder baseString = new StringBuilder();
    baseString.append(encode(httpMethod)).append("amp;").append(encode(url)).append("amp;");
    baseString.append(encode(normalizeParams(url)));
    // the extra amp; is added here.
    SecretKeySpec signingKey = new SecretKeySpec((this.apiSecret   "amp;").getBytes(), SIGN_ALG);
    Mac mac = null;
    try {
      mac = Mac.getInstance(SIGN_ALG);
      mac.init(signingKey);
      return new String(Base64.encodeBase64(
          mac.doFinal(baseString.toString().getBytes(
              StandardCharsets.UTF_8))));
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
      logger.error("Error during signing headers", e);
      return null;
    }
  }
  

Дайте мне знать, работает ли это для вас.

Комментарии:

1. Спасибо, я обязательно дам вам знать