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

时间:2017-12-29 22:32:41

标签: node.js express twitter oauth twitter-oauth

我一直在尝试在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');
}

1 个答案:

答案 0 :(得分:0)

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

  1. 获取请求令牌
  2. 使用auth URL打开twitter auth屏幕给用户
  3. 获取访问令牌
  4. 我使用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
相关问题