Skip to content
This repository was archived by the owner on Feb 13, 2018. It is now read-only.

Commit 4b1a410

Browse files
author
Tim Whitlock
committed
added support for oauth2 bearer tokens (app-only auth)
1 parent a08e04f commit 4b1a410

File tree

3 files changed

+131
-33
lines changed

3 files changed

+131
-33
lines changed

examples/oauth-app.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Example of getting application level bearer token, making an oauth 2 call, and invalidating afterwards.
3+
*/
4+
5+
6+
var consumerKey = 'your consumer key',
7+
consumerSec = 'your consumer secret';
8+
9+
var sys = require('sys'),
10+
client = require('../lib/twitter').createClient();
11+
12+
// OAuth 1a required to fetch bearer token.
13+
client.setAuth ( consumerKey, consumerSec );
14+
15+
client.fetchBearerToken( function( bearer, raw, status ){
16+
17+
if( ! bearer ){
18+
console.error('Status '+status+', failed to fetch bearer token');
19+
//console.error( sys.inspect(raw) );
20+
return;
21+
}
22+
23+
client.setAuth( bearer );
24+
console.log( 'Have OAuth 2 bearer token: ' + bearer );
25+
26+
// test a call with the new bearer token - show application rate limits for user methods
27+
client.get('application/rate_limit_status', { resources: 'users' }, function( data, error, status ){
28+
29+
if( error ){
30+
console.error('Status '+status+', failed to fetch application rate limit status');
31+
//console.error( sys.inspect(error) );
32+
return;
33+
}
34+
35+
console.log( sys.inspect(data.resources.users) );
36+
37+
// back to OAuth 1 to invalidate
38+
client.setAuth ( consumerKey, consumerSec );
39+
console.log('Invalidating token ..' );
40+
client.invalidateBearerToken( bearer, function( nothing, raw, status ){
41+
if( 200 !== status ){
42+
console.error('Status '+status+', failed to invalidate bearer token');
43+
return;
44+
}
45+
console.log('Done.');
46+
} );
47+
} );
48+
} );

lib/twitter.js

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
//
99
var TWITTER_API_TIMEOUT = 10000,
1010
TWITTER_API_USERAGENT = 'Node/'+process.version.substr(1),
11-
TWITTER_API_BASE = 'https://api.twitter.com/1.1';
12-
TWITTER_STREAM_BASE = 'https://stream.twitter.com/1.1';
13-
TWITTER_OAUTH_REQUEST_TOKEN_URL = 'https://twitter.com/oauth/request_token';
14-
TWITTER_OAUTH_AUTHORIZE_URL = 'https://twitter.com/oauth/authorize';
15-
TWITTER_OAUTH_AUTHENTICATE_URL = 'https://twitter.com/oauth/authenticate';
16-
TWITTER_OAUTH_ACCESS_TOKEN_URL = 'https://twitter.com/oauth/access_token';
11+
TWITTER_API_BASE = 'https://api.twitter.com/1.1',
12+
TWITTER_STREAM_BASE = 'https://stream.twitter.com/1.1',
13+
TWITTER_OAUTH_REQUEST_TOKEN_URL = 'https://twitter.com/oauth/request_token',
14+
TWITTER_OAUTH_AUTHORIZE_URL = 'https://twitter.com/oauth/authorize',
15+
TWITTER_OAUTH_AUTHENTICATE_URL = 'https://twitter.com/oauth/authenticate',
16+
TWITTER_OAUTH_ACCESS_TOKEN_URL = 'https://twitter.com/oauth/access_token',
17+
TWITTER_OAUTH2_BEARER_TOKEN_URL = 'https://api.twitter.com/oauth2/token',
18+
TWITTER_OAUTH2_INVALIDATE_URL = 'https://api.twitter.com/oauth2/invalidate_token';
19+
1720

1821

1922

@@ -33,6 +36,10 @@ OAuthToken.prototype.getAuthorizationUrl = function(){
3336
return TWITTER_OAUTH_AUTHORIZE_URL+'?oauth_token='+encodeURIComponent(this.key);
3437
}
3538

39+
OAuthToken.prototype.getBasicAuthHeader = function(){
40+
var creds = new Buffer( encodeURIComponent(this.key)+':'+encodeURIComponent(this.secret) );
41+
return 'Basic '+ creds.toString('base64');
42+
}
3643

3744

3845

@@ -41,16 +48,22 @@ OAuthToken.prototype.getAuthorizationUrl = function(){
4148
* Object for compiling, signing and serializing OAuth parameters
4249
*/
4350
function OAuthParams( args ){
51+
this.bearer_token = '';
4452
this.consumer_secret = '';
4553
this.token_secret = '';
4654
this.args = args || {};
47-
this.args.oauth_version = this.args.oauth_version || '1.0';
48-
55+
}
56+
57+
OAuthParams.prototype.setBearer = function( bearer ){
58+
this.bearer_token = bearer;
59+
delete this.args.oauth_version;
60+
return this;
4961
}
5062

5163
OAuthParams.prototype.setConsumer = function( token ){
5264
this.consumer_secret = token.secret||'';
5365
this.args.oauth_consumer_key = token.key||'';
66+
this.args.oauth_version = '1.0';
5467
return this;
5568
}
5669

@@ -94,6 +107,11 @@ OAuthParams.prototype.sign = function( requestMethod, requestUri ){
94107
}
95108

96109
OAuthParams.prototype.getHeader = function(){
110+
// OAuth 2
111+
if( this.bearer_token ){
112+
return 'Bearer '+encodeURIComponent(this.bearer_token);
113+
}
114+
// OAuth 1
97115
var a, args = {}, lines = [];
98116
for( a in this.args ){
99117
if( 0 === a.indexOf('oauth_') ){
@@ -118,9 +136,6 @@ OAuthParams.prototype.getHeader = function(){
118136
*/
119137
function TwitterClient(){
120138
this.deAuth();
121-
// register rate limits for all REST calls
122-
this.lastCall = null;
123-
this.lastMeta = {};
124139
}
125140

126141
TwitterClient.prototype.getLastMeta = function( method ){
@@ -139,24 +154,32 @@ TwitterClient.prototype.getRateLimitReset = function( method ){
139154
return new Date( this.getLastMeta(method).reset * 1000 );
140155
}
141156

142-
TwitterClient.prototype.setAuth = function( consumerKey, consumerSecret, accessKey, accessSecret ){
143-
this.consumerToken = new OAuthToken( consumerKey, consumerSecret );
157+
TwitterClient.prototype.setAuth = function( consumerOrBearer, consumerSecret, accessKey, accessSecret ){
158+
this.deAuth();
159+
// OAuth 2
160+
if( 1 === arguments.length ){
161+
this.bearerToken = consumerOrBearer;
162+
return this;
163+
}
164+
// OAuth 1
165+
this.consumerToken = new OAuthToken( consumerOrBearer, consumerSecret );
144166
if( accessKey || accessSecret ){
145167
this.accessToken = new OAuthToken( accessKey, accessSecret );
146168
}
147-
else {
148-
this.accessToken = null;
149-
}
150169
return this;
151170
}
152171

153172
TwitterClient.prototype.hasAuth = function(){
154-
return ( this.accessToken instanceof OAuthToken ) && ( this.consumerToken instanceof OAuthToken );
173+
return ( this.bearerToken && this.bearerToken.length ) || ( this.accessToken instanceof OAuthToken ) && ( this.consumerToken instanceof OAuthToken );
155174
}
156175

157176
TwitterClient.prototype.deAuth = function(){
177+
this.bearerToken = null;
158178
this.consumerToken = null;
159179
this.accessToken = null;
180+
// register rate limits for all REST calls
181+
this.lastCall = null;
182+
this.lastMeta = {};
160183
return this;
161184
}
162185

@@ -180,7 +203,7 @@ TwitterClient.prototype._rest = function( requestMethod, requestPath, requestArg
180203
}
181204
this.lastCall = requestPath;
182205
var client = this;
183-
return this.call( requestMethod, requestUri, requestArgs, TWITTER_API_TIMEOUT, function( res, err ) {
206+
return this._call( requestMethod, requestUri, requestArgs, '', TWITTER_API_TIMEOUT, function( res, err ) {
184207
if( ! res ){
185208
callback( null, err, 0 );
186209
return;
@@ -246,7 +269,7 @@ TwitterClient.prototype.stream = function( requestPath, requestArgs, callback ){
246269
};
247270
}
248271
var client = this;
249-
return this.call( requestMethod, requestUri, requestArgs, 0, function( res, err ){
272+
return this._call( requestMethod, requestUri, requestArgs, '', 0, function( res, err ){
250273
if( ! res ){
251274
callback( null, err );
252275
return;
@@ -269,20 +292,27 @@ TwitterClient.prototype.stream = function( requestPath, requestArgs, callback ){
269292
} );
270293
}
271294

272-
TwitterClient.prototype.call = function( requestMethod, requestUri, requestArgs, timeout, callback ){
295+
TwitterClient.prototype._call = function( requestMethod, requestUri, requestArgs, authHeader, timeout, callback ){
273296
requestMethod = String( requestMethod||'GET' ).toUpperCase();
274297
// build and sign request parameters
275298
var params = new OAuthParams( requestArgs );
276-
this.consumerToken && params.setConsumer( this.consumerToken );
277-
this.accessToken && params.setAccess( this.accessToken );
278-
params.sign( requestMethod, requestUri );
299+
if( this.bearerToken ){
300+
params.setBearer( this.bearerToken );
301+
}
302+
else {
303+
this.consumerToken && params.setConsumer( this.consumerToken );
304+
this.accessToken && params.setAccess( this.accessToken );
305+
}
279306
// grab authorization header and any remaining params
280-
var oauth = params.getHeader(),
281-
query = params.serialize();
307+
if( ! authHeader ){
308+
params.sign( requestMethod, requestUri );
309+
authHeader = params.getHeader();
310+
}
311+
var query = params.serialize();
282312
// build http request starting with parsed endpoint
283313
var http = require('url').parse( requestUri );
284314
http.headers = {
285-
Authorization: oauth,
315+
Authorization: authHeader,
286316
'User-Agent': TWITTER_API_USERAGENT
287317
};
288318
if( 'POST' === requestMethod ){
@@ -330,22 +360,36 @@ TwitterClient.prototype.abort = function(){
330360
TwitterClient.prototype.fetchRequestToken = function( url, callback ){
331361
var requestUri = TWITTER_OAUTH_REQUEST_TOKEN_URL,
332362
requestArgs = { oauth_callback: url||'oob' };
333-
return this._oauthExchange( requestUri, requestArgs, callback );
363+
return this._oauthExchange( requestUri, requestArgs, '', callback );
334364
}
335365

336366
TwitterClient.prototype.fetchAccessToken = function( verifier, callback ){
337367
var requestUri = TWITTER_OAUTH_ACCESS_TOKEN_URL,
338368
requestArgs = { oauth_verifier: verifier };
339-
return this._oauthExchange( requestUri, requestArgs, callback );
369+
return this._oauthExchange( requestUri, requestArgs, '', callback );
340370
}
341371

342-
TwitterClient.prototype._oauthExchange = function( requestUri, requestArgs, callback ){
372+
TwitterClient.prototype.fetchBearerToken = function( callback ){
373+
var requestUri = TWITTER_OAUTH2_BEARER_TOKEN_URL,
374+
requestArgs = { grant_type: 'client_credentials' },
375+
authHeader = this.consumerToken.getBasicAuthHeader();
376+
return this._oauthExchange( requestUri, requestArgs, authHeader, callback );
377+
}
378+
379+
TwitterClient.prototype.invalidateBearerToken = function( bearer, callback ){
380+
var requestUri = TWITTER_OAUTH2_INVALIDATE_URL,
381+
requestArgs = { access_token: bearer },
382+
authHeader = this.consumerToken.getBasicAuthHeader();
383+
return this._oauthExchange( requestUri, requestArgs, authHeader, callback );
384+
}
385+
386+
TwitterClient.prototype._oauthExchange = function( requestUri, requestArgs, authHeader, callback ){
343387
if( 'function' !== typeof callback ){
344388
callback = function(){
345389
console.error('No callback for POST '+requestUri);
346390
}
347391
}
348-
this.call( 'POST', requestUri, requestArgs, TWITTER_API_TIMEOUT, function( res, err ){
392+
this._call( 'POST', requestUri, requestArgs, authHeader, TWITTER_API_TIMEOUT, function( res, err ){
349393
if( ! res ){
350394
callback( null, err||{}, 0 );
351395
return;
@@ -357,8 +401,14 @@ TwitterClient.prototype._oauthExchange = function( requestUri, requestArgs, call
357401
body += chunk;
358402
} );
359403
res.on('end', function(){
360-
var params = require('querystring').parse( body );
361-
var token = params.oauth_token && params.oauth_token_secret && new OAuthToken( params.oauth_token, params.oauth_token_secret );
404+
var token,
405+
params = 0 === body.indexOf('{') ? JSON.parse(body) : body.require('querystring').parse(body);
406+
if( 'bearer' === params.token_type ){
407+
token = decodeURIComponent( params.access_token );
408+
}
409+
else if( params.oauth_token && params.oauth_token_secret ){
410+
token = new OAuthToken( params.oauth_token, params.oauth_token_secret );
411+
}
362412
callback( token, params, res.statusCode );
363413
} );
364414
} );

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "twitter-api",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"author" : "Tim Whitlock (http://timwhitlock.info)",
55
"description" : "Twitter API 1.1 client supporting REST and streaming",
66
"keywords" : [ "twitter", "tweet", "streaming", "rest", "api", "oauth" ],

0 commit comments

Comments
 (0)