Twitter OAuth1.0失败 - “需要授权”

问题描述 投票:0回答:1

我一直在尝试在Node.js中构建一个Twitter登录,以便更好地理解OAuth1.0并可能调试我在Twitter Passport上遇到的一些问题,但我已经陷入困境。

从文档中,用户身份验证以对令牌的请求开始。

我已经创建了我的Twitter应用程序并设置了回调网址。我遇到的麻烦来自于创建我的签名并提交我的第一个请求。

如果我把标题放在任何不同的顺序,我得到一个400 Bad Request错误。但是通过下面的设置,我得到401 Authorization Required错误。我不确定我的错误发现在哪里。我觉得它涉及签名本身和我的编码。

我非常感谢这里的任何方向。

编码

对于我的编码,我使用了一个代码块 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent - 到polyfill。代码看起来像这样:

function fixedEncodeURIComponent(str) {
  return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
      return '%' + c.charCodeAt(0).toString(16);
  });
}

但是我在OAuth npm package的代码库中遇到了这个相当自以为是的评论,其结果是一个字符串已经通过encodeURIComponent()运行:

// Fix the mismatch between OAuth's  RFC3986's and Javascript's beliefs in what is right and wrong ;)
return result.replace(/\!/g, "%21")
             .replace(/\'/g, "%27")
             .replace(/\(/g, "%28")
             .replace(/\)/g, "%29")
             .replace(/\*/g, "%2A");

我选择了后者。

NTP时间

我还读了一些SO提问,提到客户端时间和服务器时间之间可能存在脱节,所以我实现了ntp-client npm包。

相关守则

请让我知道你看到了什么(注意:我正在导入momentaxioscrypto,我正在导出我的所有功能,我只是把这些线留在这里):

server.js

const nonce = require('./utils/nonce')();
const timestamp = require('./utils/timestamp')();

Promise.all([nonce, timestamp]).then(function(values) {

const axios = require('axios');
const percentalizedURIComponent = require('./utils/percentalize');

const oauth_url = 'https://api.twitter.com/oauth/request_token'
const oauth_method = 'POST';
const oauth_callback = process.env.TWITTER_CALLBACK;
const oauth_consumer_key = process.env.TWITTER_CONSUMER_KEY;
const oauth_nonce = values[0];
const oauth_signature_method = 'HMAC-SHA1';
const oauth_timestamp = values[1];
const oauth_version = '1.0';
const oauth_signature = require('./utils/signature')(oauth_url, oauth_method, 
    [process.env.TWITTER_CONSUMER_SECRET], 
    { oauth_callback, oauth_consumer_key, oauth_nonce, oauth_signature_method,
      oauth_timestamp, oauth_version });

console.log({ oauth_signature: oauth_signature });

axios({
    method: oauth_method,
    url: oauth_url,
    headers: {
        Authorization: "OAuth oauth_callback=\"" +
            percentalizedURIComponent(oauth_callback) + "\", oauth_consumer_key=\"" +
            percentalizedURIComponent(oauth_consumer_key) + "\", oauth_nonce=\"" +
            percentalizedURIComponent(oauth_nonce) + "\", oauth_signature=\"" +
            percentalizedURIComponent(oauth_signature) + "\", oauth_signature_method=\"" +
            percentalizedURIComponent(oauth_signature_method) + "\", oauth_timestamp=\"" +
            percentalizedURIComponent(oauth_timestamp) + "\", oauth_version=\"" +
            percentalizedURIComponent(oauth_version) + "\"",
        Host: 'api.twitter.com'
    }
    }).then(function(response) {
        console.log({ tokenResponse: response })
    }).catch(function(error) {
        console.error({ twitterTokenError: error })
    });
}).catch(function(err) { console.error({ createSignatureError: err }) });

percentalize.js(编码)

function percentalizedURIComponent(str) {
    return encodeURIComponent(str).replace(/\!/g, "%21")
        .replace(/\'/g, "%27")
        .replace(/\(/g, "%28")
        .replace(/\)/g, "%29")
        .replace(/\*/g, "%2A");
}

timestamp.js

function timestamp() {
    return new Promise(function(resolve, reject) {
        ntpClient.getNetworkTime("pool.ntp.org", 123, function(err, date) {
            if (err) 
                return reject(err);
            return resolve(parseInt(moment(date).format("X")));
        });
    })
}

signature.js

/**
 * Function that takes in raw data and outputs a cryptographic signature
 * @param {String} url - endpoint of api being called
 * @param {String} method - HTTP method called on url
 * @param {Object[]} keys - array of signing keys
 * @param {Object} oauth - headers to be encoded into singature
 * @returns {String} hashed signature of data being sent to api
 */
function createSignature(url, method, keys = [], oauth = {}) {
    const headers = Object.keys(oauth);
    const paramString = headers.map(function(header) {
        return percentalizedURIComponent(header) + '=' + percentalizedURIComponent(oauth[header]);
    }).join("&");
    const baseString = method.toUpperCase() + '&' + percentalizedURIComponent(url) + '&' + percentalizedURIComponent(paramString);
    const hmac = crypto.createHmac(algorithm, keys.join('&'));
    hmac.update(baseString);
    return hmac.digest('base64');
}
node.js express twitter oauth twitter-oauth
1个回答
0
投票

对于Twitter OAuth1.0步骤,您需要执行以下步骤。

  1. 获取请求令牌
  2. 使用auth URL向用户打开twitter auth屏幕
  3. 获取访问令牌

我使用node.js作为我的React Native应用程序的服务器。我希望这些代码可以帮到你。

const express = require('express');
const fetch = require('node-fetch');
const Crypto = require('crypto-js');

const app = express();

// Twitter keys
const twitterConsumerSecret = <YOUR_CONSUMER_SECRET>;
const twitterConsumerKey = <YOUR_CONSUMER_KEY>;

// URL + Routes
const requestTokenURL = '/oauth/request_token';
const authorizationURL = '/oauth/authorize';
const accessURL = '/oauth/access_token';
const baseURL = 'https://api.twitter.com';


 // Callback URL of your application, change if standalone. Otherwise this is the one for in Exponent apps.
const callbackURL = <YOUR_CALLBACK_URL>/redirect';

app.listen(3000, () => {
  console.log('Twitter login app listening port 3000');
});

app.get('/redirect_url', (req, res) => {
  // Set response to JSON
  res.setHeader('Content-Type', 'application/json');

  // Request Token
  // Creates base header + Request URL
  const tokenRequestHeaderParams = createHeaderBase();
  const tokenRequestURL = baseURL + requestTokenURL;

  // Add additional parameters for signature + Consumer Key
  tokenRequestHeaderParams.oauth_consumer_key = twitterConsumerKey;

  // Creates copy to add additional request params, to then create the signature
  const callBackParam = { oauth_callback: callbackURL };
  const parametersForSignature = Object.assign({}, callBackParam, tokenRequestHeaderParams);
  const signature = createSignature(parametersForSignature, 'POST', tokenRequestURL, twitterConsumerSecret);
  tokenRequestHeaderParams.oauth_signature = signature;
  // Creates the Header String, adds the callback parameter
  const headerString = createHeaderString(tokenRequestHeaderParams);
  const callbackKeyValue = ', oauth_callback="' + encodeURIComponent(callbackURL) + '"';
  const tokenRequestHeader = headerString + callbackKeyValue;
  // Request
  fetch(tokenRequestURL, { method: 'POST', headers: { Authorization: tokenRequestHeader } })
    .then(response => {
      return response.text();
    }).then(response => {
      const tokenResponse = parseFormEncoding(response);
      const authToken = tokenResponse.oauth_token;
      const authTokenSecret = tokenResponse.oauth_token_secret;
      // Token Authorization, send the URL to the native app to then display in 'Webview'
      const authURL = baseURL + authorizationURL + '?oauth_token=' + authToken;
      res.json({ redirectURL: authURL, token: authToken, secretToken: authTokenSecret });
    });
});

// Requires oauth_verifier
app.get('/access_token', (req, res) => {
  const verifier = req.query.oauth_verifier;
  const authToken = req.query.oauth_token;
  const secretToken = req.query.oauth_secret_token;
  // Creates base header + Access Token URL
  const accessTokenHeaderParams = createHeaderBase();
  const accessTokenURL = baseURL + accessURL;

  // Add additional parameters for signature + Consumer Key
  accessTokenHeaderParams.oauth_consumer_key = twitterConsumerKey;
  accessTokenHeaderParams.oauth_token = authToken;
  accessTokenHeaderParams.oauth_token_secret = secretToken;

  const accessTokenSignature = createSignature(accessTokenHeaderParams, 'POST', accessTokenURL, twitterConsumerSecret);
  accessTokenHeaderParams.oauth_signature = accessTokenSignature;

  // Creates the Header String, adds the oauth verfier
  const accessTokenHeaderString = createHeaderString(accessTokenHeaderParams);
  const verifierKeyValue = ', oauth_verifier="' + encodeURIComponent(verifier) + '"';
  const accessTokenRequestHeader = accessTokenHeaderString + verifierKeyValue;
  // Convert token to Access Token
  fetch(accessTokenURL, { method: 'POST', headers: { Authorization: accessTokenRequestHeader } })
  .then(response => {
    return response.text();
  }).then(response => {
    const accessTokenResponse = parseFormEncoding(response);
    res.json({ accessTokenResponse });
  });
});

/**
 * Parse a form encoded string into an object
 * @param  {string} formEncoded Form encoded string
 * @return {Object}             Decoded data object
 */
function parseFormEncoding(formEncoded) {
  const pairs = formEncoded.split('&');
  const result = {};
  for (const pair of pairs) {
    const [key, value] = pair.split('=');
    result[key] = value;
  }
  return result;
}

/**
 * Creates the Token Request OAuth header
 * @param  {Object} params OAuth params
 * @return {string}        OAuth header string
 */
function createHeaderString(params) {
  return 'OAuth ' + Object.keys(params).map(key => {
    const encodedKey = encodeURIComponent(key);
    const encodedValue = encodeURIComponent(params[key]);
    return `${encodedKey}="${encodedValue}"`;
  }).join(', ');
}

/**
 * Creates the Signature for the OAuth header
 * @param  {Object}  params         OAuth + Request Parameters
 * @param  {string}  HTTPMethod     Type of Method (POST,GET...)
 * @param  {string}  requestURL     Full Request URL
 * @param  {string}  consumerSecret Twitter Consumer Secret
 * @param  {?string} tokenSecret    Secret token (Optional)
 * @return {string}                 Returns the encoded/hashed signature
 */
function createSignature(params, httpMethod, requestURL, consumerSecret, tokenSecret) {
  const encodedParameters = percentEncodeParameters(params);
  const upperCaseHTTPMethod = httpMethod.toUpperCase();
  const encodedRequestURL = encodeURIComponent(requestURL);
  const encodedConsumerSecret = encodeURIComponent(consumerSecret);

  const signatureBaseString = upperCaseHTTPMethod +
    '&' + encodedRequestURL +
    '&' + encodeURIComponent(encodedParameters);

  let signingKey;
  if (tokenSecret !== undefined) {
    signingKey = encodedRequestURL + '&' + encodeURIComponent(tokenSecret);
  } else {
    signingKey = encodedConsumerSecret + '&';
  }
  const signature = Crypto.HmacSHA1(signatureBaseString, signingKey);
  const encodedSignature = Crypto.enc.Base64.stringify(signature);
  return encodedSignature;
}

/**
 * Percent encode the OAUTH Header + Request parameters for signature
 * @param  {Object} params Dictionary of params
 * @return {string}        Percent encoded parameters string
 */
function percentEncodeParameters(params) {
  return Object.keys(params).map(key => {
    const encodedKey = encodeURIComponent(key);
    const encodedValue = encodeURIComponent(params[key]);
    return `${encodedKey}=${encodedValue}`;
  }).join('&');
}

/**
 * Creates the header with the base parameters (Date, nonce etc...)
 * @return {Object} returns a header dictionary with base fields filled.
 */
function createHeaderBase() {
  return {
    oauth_consumer_key: '',
    oauth_nonce: createNonce(),
    oauth_signature_method: 'HMAC-SHA1',
    oauth_timestamp: new Date().getTime() / 1000,
    oauth_version: '1.0',
  };
}

/**
 * Creates a nonce for OAuth header
 * @return {string} nonce
 */
function createNonce() {
  let text = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  for (let i = 0; i < 32; i += 1) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

我首先调用redirect_url来获取请求令牌,请求令牌密钥和auth url。然后让用户授予您的应用权限。然后使用这些参数获取该用户的访问令牌。 oauth_verifier,oauth_token,oauth_secret_token成功获得权限后,twitter会提供这些信息。然后从我的主机调用access_token url

© www.soinside.com 2019 - 2024. All rights reserved.