我一直在尝试在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包。
相关代码
请告诉我你的内容(注意:我正在导入moment
,axios
,crypto
,我正在导出我的所有功能,我只是将这些内容排除在外这里):
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');
}
答案 0 :(得分:0)
对于Twitter OAuth1.0步骤,您需要执行以下步骤。
我使用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