This commit is contained in:
Matias Woloski
2013-06-30 22:27:13 -03:00
commit 07d163b2b1
10 changed files with 434 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
node_modules
.DS_Store
+4
View File
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.8
- 0.10
+121
View File
@@ -0,0 +1,121 @@
# node-jws [![Build Status](https://secure.travis-ci.org/auth0/node-jsonwebtoken.png)](http://travis-ci.org/auth0/node-jsonwebtoken)
An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html).
This was developed against `draft-ietf-oauth-json-web-token-08`. It makes use of [node-jws](https://github.com/brianloveswords/node-jws)
# Install
```bash
$ npm install jsonwebtoken
```
# Usage
## jwt.sign(payload, secretOrPrivateKey, options)
(Synchronous) Returns the JsonWebToken as string
`payload` could be an literal, buffer or string
`secretOrPrivateKey` is a string or buffer containing either the secret for HMAC algorithms, or the PEM
encoded private key for RSA and ECDSA.
`options`:
* `algorithm` (default: `HS256`)
* `expiresInMinutes`
* `audience`
* `subject`
* `issuer`
If `payload` is not a buffer or a string, it will be coerced into a string
using `JSON.stringify`.
If any `expiresInMinutes`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.
Example
```js
// sign with default (HMAC SHA256)
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
// sign with RSA SHA256
var cert = fs.readFileSync('private.key'); // get private key
var token = jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256'});
```
## jwt.verify(token, secretOrPublicKey, options, callback)
(Synchronous with callback) Returns the payload decoded if the signature (and optionally expiration, audience, issuer) are valid. If not, it will return the error.
`token` is the JsonWebToken string
`secretOrPublicKey` is a string or buffer containing either the secret for HMAC algorithms, or the PEM
encoded public key for RSA and ECDSA.
`options`
* `audience`: if you want to check audience (`aud`), provide a value here
* `issuer`: if you want to check issuer (`iss`), provide a value here
```js
// verify a token symmetric
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
// invalid token
jwt.verify(token, 'wrong-secret', function(err, decoded) {
// err
// decoded undefined
});
// verify a token asymmetric
var cert = fs.readFileSync('public.pem'); // get public key
jwt.verify(token, cert, function(err, decoded) {
console.log(decoded.foo) // bar
});
// verify audience
var cert = fs.readFileSync('public.pem'); // get public key
jwt.verify(token, cert, { audience: 'urn:foo' }, function(err, decoded) {
// if audience mismatch, err == invalid audience
});
// verify issuer
var cert = fs.readFileSync('public.pem'); // get public key
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer' }, function(err, decoded) {
// if issuer mismatch, err == invalid issuer
});
```
## Algorithms supported
Array of supported algorithms. The following algorithms are currently supported.
alg Parameter Value | Digital Signature or MAC Algorithm
----------------|----------------------------
HS256 | HMAC using SHA-256 hash algorithm
HS384 | HMAC using SHA-384 hash algorithm
HS512 | HMAC using SHA-512 hash algorithm
RS256 | RSASSA using SHA-256 hash algorithm
RS384 | RSASSA using SHA-384 hash algorithm
RS512 | RSASSA using SHA-512 hash algorithm
ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm
ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm
ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm
none | No digital signature or MAC value included
# TODO
* X.509 certificate chain is not checked
# License
MIT
+62
View File
@@ -0,0 +1,62 @@
var jws = require('jws');
var moment = require('moment');
module.exports.sign = function(payload, secretOrPrivateKey, options) {
options = options || {};
var header = {typ: 'JWT', alg: options.algorithm || 'HS256'};
if (options.expiresInMinutes)
payload.exp = moment().add('minutes', options.expiresInMinutes).utc().unix();
if (options.audience)
payload.aud = options.audience;
if (options.issuer)
payload.iss = options.issuer;
if (options.subject)
payload.sub = options.subject;
payload.iat = moment().utc().unix();
var signed = jws.sign({header: header, payload: payload, secret: secretOrPrivateKey});
return signed;
};
module.exports.verify = function(jwtString, secretOrPublicKey, options, callback) {
if ((typeof options === 'function') && !callback) callback = options;
if (!options) options = {};
var valid;
try {
valid = jws.verify(jwtString, secretOrPublicKey);
}
catch (e) {
return callback(e);
}
if (!valid)
return callback(new Error('invalid signature'));
var jwt = jws.decode(jwtString);
if (jwt.payload.exp) {
if (moment().utc().unix() >= jwt.payload.exp)
return callback(new Error('jwt expired'));
}
if (jwt.payload.aud && options.audience) {
if (jwt.payload.aud !== options.audience)
return callback(new Error('jwt audience invalid. expected: ' + jwt.payload.aud));
}
if (jwt.payload.iss && options.issuer) {
if (jwt.payload.iss !== options.issuer)
return callback(new Error('jwt issuer invalid. expected: ' + jwt.payload.iss));
}
callback(null, jwt.payload);
};
+27
View File
@@ -0,0 +1,27 @@
{
"name": "node-jsonwebtoken",
"version": "0.1.0",
"description": "JSON Web Token implementation (symmetric and asymmetric)",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"repository": {
"type": "git",
"url": "https://github.com/auth0/node-jsonwebtoken"
},
"keywords": [
"jwt"
],
"author": "auth0",
"license": "MIT",
"bugs": {
"url": "https://github.com/auth0/node-jsonwebtoken/issues"
},
"dependencies": {
"jws": "~0.2.2"
},
"devDependencies": {
"chai": "*"
}
}
+19
View File
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDJjCCAg6gAwIBAgIJAMyz3mSPlaW4MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMUCyouYXV0aDAuY29tMB4XDTEzMDQxODE3MDE1MFoXDTI2MTIyNjE3MDE1MFow
FjEUMBIGA1UEAxQLKi5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDZq1Ua0/BGm+TaBFoftKWeYMWrQG9Fx3g7ikErxljmyOvlwqkiat3q
ixX+Dxw9TFb5gbBjNJ+L3nt4YefJgLsYvsHqkOUxWsB+HM/ulJRVnVrZm1tI3Nbg
xO1BQ7DrGfBpq2KCxtQCaQFRlQJw1+qS5LwrdIvihB7Kc142VElCFFHJ6+09eMUy
jy00Z5pfQr4Am6W6eEOS9ObDbNs4XgKOcWe5khWXj3UStou+VgbAg40XcYht2IbY
gMfKF+VUZOy3+e+aRTqPOBU3MAeb0tvCCPUQJbNAUHgSKVhAvNf8mRwttVsOLT70
anjjeCOd7RKS8fVKBwc2KtgNkghYdPY9AgMBAAGjdzB1MB0GA1UdDgQWBBSi4+X0
+MvCKDdd375mDhx/ZBbJ4DBGBgNVHSMEPzA9gBSi4+X0+MvCKDdd375mDhx/ZBbJ
4KEapBgwFjEUMBIGA1UEAxQLKi5hdXRoMC5jb22CCQDMs95kj5WluDAMBgNVHRME
BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBi0qPe0DzlPSufq+Gdk2Fwf1pGEtjA
D34IxxJ9SX6r1DS/NIP7IOLUnNU8cP8BQWl7i413v29jJsNV457pjdmqf8J7OE9O
eF5Yz1x91gY/27561Iga/TQeIVOlFQAgx66eLfUFFoAig3hz2srZo5TzYBixMJsS
fYMXHPiU7KoLUqYXvpSXIllstQCu51KCC6t9H7wZ92lTES1v76hFY4edQ30sftPo
kjAYWGEhMjPo/r4THcdSMqKXoRtCGEun4pTXid7MJcTgdGDrAJddLWi6SxKecEVB
MhMu4XfUCdxCwqQPjHeJ+zE49A1CUdBB2FN3BNLbmTTwEBgmuwyGRzhj
-----END CERTIFICATE-----
+35
View File
@@ -0,0 +1,35 @@
var jwt = require('../index');
var expect = require('chai').expect;
var assert = require('chai').assert;
describe('HS256', function() {
describe('when signing a token', function() {
var secret = 'shhhhhh';
var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' });
it('should be syntactically valid', function() {
expect(token).to.be.a('string');
expect(token.split('.')).to.have.length(3);
});
it('should validate with secret', function(done) {
jwt.verify(token, secret, function(err, decoded) {
assert.ok(decoded.foo);
assert.equal('bar', decoded.foo);
done();
});
});
it('should throw with invalid secret', function(done) {
jwt.verify(token, 'invalid secret', function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
done();
});
});
});
});
+115
View File
@@ -0,0 +1,115 @@
var jwt = require('../index');
var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;
var assert = require('chai').assert;
describe('RS256', function() {
var pub = fs.readFileSync(path.join(__dirname, 'pub.pem'));
var priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
var invalid_pub = fs.readFileSync(path.join(__dirname, 'invalid_pub.pem'));
describe('when signing a token', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });
it('should be syntactically valid', function() {
expect(token).to.be.a('string');
expect(token.split('.')).to.have.length(3);
});
it('should validate with public key', function(done) {
jwt.verify(token, pub, function(err, decoded) {
assert.ok(decoded.foo);
assert.equal('bar', decoded.foo);
done();
});
});
it('should throw with invalid public key', function(done) {
jwt.verify(token, invalid_pub, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
done();
});
});
});
describe('when signing a token with expiration', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', expiresInMinutes: 10 });
it('should be valid expiration', function(done) {
jwt.verify(token, pub, function(err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});
it('should be invalid', function(done) {
// expired token
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', expiresInMinutes: -10 });
jwt.verify(token, pub, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
done();
});
});
});
describe('when signing a token with audience', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', audience: 'urn:foo' });
it('should check audience', function(done) {
jwt.verify(token, pub, function(err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
done();
});
});
it('should throw when invalid audience', function(done) {
jwt.verify(token, pub, { audience: 'urn:wrong' }, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
done();
});
});
});
describe('when signing a token with issuer', function() {
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', issuer: 'urn:foo' });
it('should check issuer', function() {
jwt.verify(token, pub, { issuer: 'urn:foo' }, function(err, decoded) {
assert.isNotNull(decoded);
assert.isNull(err);
});
});
it('should throw when invalid issuer', function() {
jwt.verify(token, pub, { issuer: 'urn:wrong' }, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
});
});
});
describe('when verifying a malformed token', function() {
it('should throw', function(done) {
jwt.verify('fruit.fruit.fruit', pub, function(err, decoded) {
assert.isUndefined(decoded);
assert.isNotNull(err);
done();
});
});
});
});
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN
+H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXi
c78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6Gb
RKzyTKcB58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRX
kdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1K
kyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABAoIBAQCYKw05YSNhXVPk
eHLeW/pXuwR3OkCexPrakOmwMC0s2vIF7mChN0d6hvhVlUp68X7V8SnS2JxAGo8v
iHY+Et3DdwZ3cxnzwh+BEhzgDfoIOmkoGppZPyX/K6klWtbGUrTtSISOWXbvEXQU
G0qGAvDOzIGTsdMDX7slnU70Ac23JybPY5qBSiE+ky8U4dm2fUHMroWub4QP5vA/
nqyWqX2FB/MEAbcujaknDQrFCtbmtUYlBbJCKGd9V3cGEqp6H7oH+ah2ofMc91gJ
mCHk3YyWZB/bcVXH3CA+s1ywvCOVDBZ3Nw7Pt9zIcv6Rl9UKIy+Nx0QjXxR90Hla
Tr0GHIShAoGBAPsD7uXm+0ksnGyKRYgvlVad8Z8FUFT6bf4B+vboDbx40FO8O/5V
PraBPC5z8YRSBOQ/WfccPQzakkA28F2pXlRpXu5JcErVWnyyUiKpX5sw6iPenQR2
JO9hY/GFbKiwUhVHpvWMcXFqFLSQu2A86jPnFFEfG48ZT4IhTzINKJVZAoGBAMKc
B3YGfVfY9qiRFXzYRdSRLg5c8p/HzuWwXc9vfJ4kQTDkPXe/+nqD67rzeT54uVec
jKoIrsCu4BfEaoyvOT+1KmUfdEpBgYZuuEC4CZf7dgKbXOpPVvZDMyJ/e7HyqTpw
mvIYJLPm2fNAcAsnbrNX5mhLwwzEIltbplUUeRdrAoGBAKhZgPYsLkhrZRXevreR
wkTvdUfD1pbHxtFfHqROCjhnhsFCM7JmFcNtdaFqHYczQxiZ7IqxI7jlNsVek2Md
3qgaa5LBKlDmOuP67N9WXUrGSaJ5ATIm0qrB1Lf9VlzktIiVH8L7yHHaRby8fQ8U
i7b3ukaV6HPW895A3M6iyJ8xAoGAInp4S+3MaTL0SFsj/nFmtcle6oaHKc3BlyoP
BMBQyMfNkPbu+PdXTjtvGTknouzKkX4X4cwWAec5ppxS8EffEa1sLGxNMxa19vZI
yJaShI21k7Ko3I5f7tNrDNKfPKCsYMEwgnHKluDwfktNTnyW/Uk2dgXuMaXSHHN5
XZt59K8CgYArGVOWK7LUmf3dkTIs3tXBm4/IMtUZmWmcP9C8Xe/Dg/IdQhK5CIx4
VXl8rgZNeX/5/4nJ8Q3LrdLau1Iz620trNRGU6sGMs3x4WQbSq93RRbFzfG1oK74
IOo5yIBxImQOSk5jz31gF9RJb15SDBIxonuWv8qAERyUfvrmEwR0kg==
-----END RSA PRIVATE KEY-----
+22
View File
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/
QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugM
Y1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB
58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6
lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpx
aJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nk
LtoXFlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkv
cqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d
3ntlv1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/F
OZkYUv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak
/ICgdZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0Yuor
XZba9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79R
PAfbG1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+Q=
-----END CERTIFICATE-----