-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b579fea
Showing
7 changed files
with
388 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
samples/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2013, Nicolas Mercier | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# google-oauth-jwt | ||
|
||
Google API OAuth2 authentication for Server to Server applications with Node.js. Requires a Service account from the | ||
Google API console. | ||
|
||
This library generates JWT (JSON Web Token) tokens to establish identity without an end-user being involved. | ||
The tokens must be signed with a key that you need to generate from the API console. The tokens are generated from | ||
the specifications found at | ||
[https://developers.google.com/accounts/docs/OAuth2ServiceAccount](https://developers.google.com/accounts/docs/OAuth2ServiceAccount). | ||
|
||
It also integrates with [https://github.com/mikeal/request](request) to seamlessly query Google REST APIs. | ||
|
||
## Documentation | ||
|
||
### Installation | ||
```bash | ||
npm install google-oauth-jwt | ||
``` | ||
|
||
### Generating a key to sign the tokens | ||
|
||
1. From the Google API Console, create a Service account | ||
For more help: | ||
[https://developers.google.com/console/help/#service_accounts](https://developers.google.com/console/help/#service_accounts) | ||
|
||
2. Download the generated P12 key. IMPORTANT: keep a copy of the key, Google keeps only the public key. | ||
|
||
3. Convert the key to PEM, so we can use it from the Node crypto module. | ||
To do this, run the following in Terminal: | ||
```bash | ||
openssl pkcs12 -in downloaded-key-file.p12 -out your-key-file.pem -nodes | ||
``` | ||
The password for the key is "notasecret", as mentioned when you downloaded the key. | ||
|
||
### Granting access to resources that can be requested through an API | ||
|
||
In order to query resources using the APIs, access must be granted to the Service Account. Each Google application that | ||
has security settings must be configured individually. Access is granted using the email address of the service account. | ||
|
||
For example, in order to list files in Google Drive, folders and files must be shared with the Service Account, by using | ||
its email address. | ||
|
||
Same goes for Google Calendar, Google Contacts, etc. | ||
|
||
### Querying a RESTful Google API | ||
|
||
In this example, we will use a modified instance of [https://github.com/mikeal/request](request) to query the | ||
Google Drive API. This modified instance allows to automatically request and cache the token. | ||
|
||
Note that the request options object includes a "jwt" setting to specify how to request the token. The token will | ||
automatically be generated and inserted in the querystring for this API call. The token will also be cached and | ||
reused for subsequent calls using the same service account and scopes. | ||
|
||
```javascript | ||
var request = require('google-oauth-jwt').requestWithJWT(); | ||
|
||
request({ | ||
url: 'https://www.googleapis.com/drive/v2/files', | ||
jwt: { | ||
// use the email address of the service account, as indicated in the API console | ||
email: 'my-service-account@developer.gserviceaccount.com', | ||
// use the PEM file we generated from the downloaded key | ||
keyFile: 'my-service-account-key.pem', | ||
// specify the scopes you which to access | ||
scopes: ['https://www.googleapis.com/auth/drive.readonly'] | ||
} | ||
}, function (err, res, body) { | ||
|
||
console.log(JSON.parse(body)); | ||
|
||
}); | ||
``` | ||
|
||
### Requesting the token manually | ||
|
||
If you wish to simply request the token for use with a Google API, use the 'authenticate' method. | ||
|
||
```javascript | ||
var googleAuth = require('google-oauth-jwt'); | ||
|
||
googleAuth.authenticate({ | ||
// use the email address of the service account, as indicated in the API console | ||
email: 'my-service-account@developer.gserviceaccount.com', | ||
// use the PEM file we generated from the downloaded key | ||
keyFile: 'my-service-account-key.pem', | ||
// specify the scopes you which to access | ||
scopes: ['https://www.googleapis.com/auth/drive.readonly'] | ||
}, function (err, token) { | ||
|
||
console.log(token); | ||
|
||
}); | ||
``` | ||
|
||
### Specifying options | ||
|
||
The following options can be specified in order to generate the JWT: | ||
|
||
```javascript | ||
var options = { | ||
// the email address of the service account (required) | ||
email: 'my-service-account@developer.gserviceaccount.com', | ||
// an array of scopes uris to request access to (required) | ||
scopes: [...], | ||
// the cryptographic key as a string, can be the contents of the PEM file | ||
key: 'KEY_CONTENTS', | ||
// the path to the PEM file to use for the cryptographic key (ignored is 'key' is defined) | ||
keyFile: 'KEY_CONTENTS', | ||
// the duration of the token in milliseconds - default is 1 hour (60 * 60 * 1000), maximum allowed by Google is 1 hour | ||
expiration: 3600000, | ||
// if access is being granted on behalf of someone else, specifies who is impersonating the service account | ||
delegationEmail: 'email_address' | ||
}; | ||
``` | ||
|
||
More information: | ||
[https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset](https://developers.google.com/accounts/docs/OAuth2ServiceAccount#formingclaimset) | ||
|
||
## Compatibility | ||
|
||
+ Tested with Node 0.8 | ||
+ Tested on Mac OS X 10.8 | ||
|
||
## Dependencies | ||
|
||
+ request | ||
|
||
## License | ||
|
||
The MIT License (MIT) | ||
|
||
Copyright (c) 2013, Nicolas Mercier | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
var auth = require('./lib/auth'), | ||
request = require('./lib/request-jwt'); | ||
|
||
exports.authenticate = auth.authenticate; | ||
exports.encodeJWT = auth.encodeJWT; | ||
exports.requestWithJWT = request.requestWithJWT; | ||
exports.resetCache = request.resetCache; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
var fs = require('fs'), | ||
crypto = require('crypto'), | ||
request = require('request'); | ||
|
||
/** | ||
* Requests a token by submitting a signed JWT. | ||
* @param options The JWT generation options. | ||
* @param callback | ||
*/ | ||
exports.authenticate = function (options, callback) { | ||
|
||
callback = callback || function () { }; | ||
|
||
exports.encodeJWT(options, function (err, jwt) { | ||
|
||
if (err) return callback(err); | ||
|
||
return request.post('https://accounts.google.com/o/oauth2/token', { | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
}, | ||
form: { | ||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', | ||
assertion: jwt | ||
} | ||
}, function (err, res, body) { | ||
|
||
if (err) return callback(err); | ||
|
||
if (res.statusCode == 200) { | ||
try { | ||
return callback(undefined, JSON.parse(body).access_token); | ||
} catch (parseErr) { | ||
return callback(parseErr); | ||
} | ||
} | ||
|
||
return callback(new Error("Unable to request an access token (HTTP " + res.statusCode + ') : ' + body)); | ||
}); | ||
}); | ||
|
||
}; | ||
|
||
/** | ||
* Encodes a JWT using the supplied options. | ||
* @param options The options to use to generate the JWT. | ||
* @param callback | ||
*/ | ||
exports.encodeJWT = function (options, callback) { | ||
|
||
var iat = Math.floor(new Date().getTime() / 1000), | ||
exp = iat + Math.floor((options.expiration || 60 * 60 * 1000) / 1000), | ||
claims = { | ||
iss: options.email, | ||
scope: options.scopes.join(' '), | ||
aud: 'https://accounts.google.com/o/oauth2/token', | ||
exp: exp, | ||
iat: iat | ||
}; | ||
|
||
if (options.delegationEmail) { | ||
claims.prn = options.delegationEmail; | ||
} | ||
|
||
var JWT_header = new Buffer(JSON.stringify({ alg: "RS256", typ: "JWT" })).toString('base64'), | ||
JWT_claimset = new Buffer(JSON.stringify(claims)).toString('base64'), | ||
unsignedJWT = [JWT_header, JWT_claimset].join('.'); | ||
|
||
obtainKey(function (err, key) { | ||
|
||
if (err) return callback(err); | ||
|
||
var JWT_signature = crypto.createSign('RSA-SHA256').update(unsignedJWT).sign(key, 'base64'), | ||
signedJWT = [unsignedJWT, JWT_signature].join('.'); | ||
|
||
return callback(undefined, signedJWT); | ||
|
||
}); | ||
|
||
function obtainKey(callback) { | ||
|
||
if (options.key) { | ||
return callback(undefined, options.key); | ||
} else if (options.keyFile) { | ||
return fs.readFile(options.keyFile, callback); | ||
} | ||
|
||
return callback(new Error('Key is not specified. Use options.key or options.keyFile to specify a key.')); | ||
} | ||
|
||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
var auth = require('./auth'), | ||
tokenCache = {}, | ||
cacheInvalidations = []; | ||
|
||
/** | ||
* Returns a JWT-enabled request module. | ||
* @param request The request instance to modify to enable JWT. | ||
* @returns {Function} The JWT-enabled request module. | ||
*/ | ||
exports.requestWithJWT = function (request) { | ||
|
||
if (!request) { | ||
// use the request module from our dependency | ||
request = require('request'); | ||
} | ||
|
||
return function (uri, options, callback) { | ||
|
||
if (typeof uri === 'undefined') throw new Error('undefined is not a valid uri or options object.'); | ||
if ((typeof options === 'function') && !callback) callback = options; | ||
if (options && typeof options === 'object') { | ||
options.uri = uri; | ||
} else if (typeof uri === 'string') { | ||
options = {uri: uri}; | ||
} else { | ||
options = uri; | ||
} | ||
if (callback) options.callback = callback; | ||
|
||
// look for a request with JWT requirements | ||
if (options.jwt) { | ||
return getToken(options.jwt, function (err, token) { | ||
// TODO: for now the token is only passed using the query string | ||
// insert the token in the query string | ||
options.qs = options.qs || {}; | ||
options.qs.access_token = token; | ||
request(uri, options, callback); | ||
}); | ||
} else { | ||
return request(uri, options, callback); | ||
} | ||
|
||
}; | ||
|
||
function getToken(options, callback) { | ||
|
||
var key = options.email + ':' + options.scopes.join('|'); | ||
|
||
if (tokenCache[key]) { | ||
// token is already available, return it now | ||
callback(null, tokenCache[key]); | ||
} else { | ||
// token must be retrieved | ||
auth.authenticate(options, function (err, token) { | ||
|
||
if (err) return callback(err); | ||
|
||
// store the token for reuse | ||
tokenCache[key] = token; | ||
|
||
// setup token expiration | ||
cacheInvalidations.push(setTimeout(function () { | ||
delete tokenCache[key]; | ||
}, (options.expiration || 60 * 60 * 1000))); | ||
|
||
return callback(null, token); | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Resets the token cache, clearing previously requested tokens. | ||
*/ | ||
exports.resetCache = function () { | ||
cacheInvalidations.forEach(function (timerId) { | ||
clearTimeout(timerId); | ||
}); | ||
tokenCache = {}; | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"name": "google-oauth-jwt", | ||
"version": "0.0.1", | ||
"author": { | ||
"name": "Nicolas Mercier", | ||
"email": "nicolas@extrabacon.net" | ||
}, | ||
"description": "Implementation of Google OAuth 2.0 for server-to-server interactions, allowing secure use of Google APIs without the end-user being involved.", | ||
"keywords": [ | ||
"google", | ||
"api", | ||
"oauth", | ||
"oauth 2.0", | ||
"oauth2", | ||
"service account", | ||
"jwt", | ||
"token", | ||
"server to server" | ||
], | ||
"dependencies": { | ||
"request": "*" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/extrabacon/google-oauth-jwt" | ||
}, | ||
"homepage": "http://github.com/extrabacon/google-oauth-jwt", | ||
"bugs": "http://github.com/extrabacon/google-oauth-jwt/issues", | ||
"licenses": [ | ||
{ | ||
"type": "MIT" | ||
} | ||
], | ||
"engines": { | ||
"node": ">=0.8" | ||
} | ||
} |