Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

jwt-authentication

Package Overview
Dependencies
Maintainers
2
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jwt-authentication - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

23

docs/API.md

@@ -50,2 +50,5 @@ #Index

##authenticator.validate(jwtToken, callback)
WARNING: This method is out of date and does not comply with the spec anymore.
Validating JWT tokens will not work.
Validates a jwt token.

@@ -82,4 +85,8 @@ The public key used for validation is retrieved from the configured

If the subject is generating tokens for itself the `sub` and `iss` should be the same.
- aud `String` - Audience. The value that identifies the resource server.
- options `Object`
- privateKey `String` - The private key to use when generating the token.
- kid `String` - Key ID. The identifier of the key used to sign the token in the format
'issuer/key-id' where issuer matches claims.iss.
- \[expiresInMinutes=0.5\] `Number` - The number of minutes until the token expires.
- callback <code>[GenerateTokenCallback](#GenerateTokenCallback)</code> - The callback that is called when the token has been generated.

@@ -89,4 +96,4 @@

```js
var claims = {iss: 'name-of-client', sub: 'name-of-client'};
var options = {privateKey: 'a-private-key'};
var claims = {iss: 'name-of-client', sub: 'name-of-client', aud: 'name-of-server'};
var options = {privateKey: 'a-private-key', kid: 'name-of-client/key-id'};
authenticator.generateToken(claims, options, function (error, token) {

@@ -104,3 +111,3 @@ if (error) {

Generates an authorization header value containing a jwt token.
The format of the value is `x-atl-jwt [token]`.
The format of the value is `Bearer [token]`.

@@ -114,4 +121,8 @@ **Params**

If the subject is generating tokens for itself the `sub` and `iss` should be the same.
- aud `String` - Audience. The value that identifies the resource server.
- options `Object`
- privateKey `String` - The private key to use when generating the token.
- kid `String` - Key ID. The identifier of the key used to sign the token in the format
'issuer/key-id' where issuer matches claims.iss.
- \[expiresInMinutes=0.5\] `Number` - The number of minutes until the token expires.
- callback <code>[GenerateAuthorizationHeaderCallback](#GenerateAuthorizationHeaderCallback)</code> - The callback that is called when the authorization header has been generated.

@@ -121,4 +132,4 @@

```js
var claims = {iss: 'name-of-client', sub: 'name-of-client'};
var options = {privateKey: 'a-private-key'};
var claims = {iss: 'name-of-client', sub: 'name-of-client', aud: 'name-of-server'};
var options = {privateKey: 'a-private-key', kid: 'name-of-client/key-id'};
authenticator.generateAuthorizationHeader(claims, options, function (error, headerValue) {

@@ -128,3 +139,3 @@ if (error) {

} else {
console.log(headerValue); // -> "x-atl-jwt [token]"
console.log(headerValue); // -> "Bearer [token]"
}

@@ -131,0 +142,0 @@ });

@@ -0,1 +1,14 @@

<a name="0.0.3"></a>
### 0.0.3 (2015-03-13)
#### Features
* Add required jti claim when generating token. ([1ca5fd9c](https://bitbucket.org/atlassianlabs/jwt-authentication/commits/1ca5fd9c723fb36e46ea9526a59263b2a6c8cc21))
* Add required 'kid' option when generating a token. ([f31f1b5d](https://bitbucket.org/atlassianlabs/jwt-authentication/commits/f31f1b5ddf67431f231842d18936082c090ae79c))
* Add 'aud' as a required claim when generating a token. ([daeffc9c](https://bitbucket.org/atlassianlabs/jwt-authentication/commits/daeffc9cb784d3b869d45c53aaf0f69d7ddb6c83))
* Change token prefix from 'x-atl-jwt' to 'Bearer'. ([1cddc8d6](https://bitbucket.org/atlassianlabs/jwt-authentication/commits/1cddc8d696b9dac2da99f1250562e26677202162))
* Add token expiry support ([50f75258](https://bitbucket.org/atlassianlabs/jwt-authentication/commits/50f752588c4500d4b4b1259b87c1e35349c610fd))
<a name="0.0.2"></a>

@@ -2,0 +15,0 @@ ### 0.0.2 (2015-02-02)

@@ -32,4 +32,8 @@ var jsonWebToken = require('./jwt-authentication/json-web-token');

var validateClaims = function(claims) {
if (!claims || !claims.iss || !claims.sub) {
return {valid: false, error: 'claims body must have both the "iss" and "sub" fields'};
var requiredClaims = ['iss', 'sub', 'aud'];
var allRequiredClaimsProvided = _.every(requiredClaims, function (claim) {
return claims && _.isString(claims[claim]);
});
if (!allRequiredClaimsProvided) {
return {valid: false, error: 'claims body must contain "iss", "sub" and "aud" fields'};
} else {

@@ -41,4 +45,4 @@ return {valid: true};

var validateOptions = function(options) {
if (!options || !options.privateKey) {
return {valid: false, error: 'Required value options.privateKey is missing'};
if (!options || !options.privateKey || !options.kid) {
return {valid: false, error: 'Options must contain "privateKey" and "kid" fields'};
} else {

@@ -58,2 +62,5 @@ return {valid: true};

/**
* WARNING: This method is out of date and does not comply with the spec anymore.
* Validating JWT tokens will not work.
*
* Validates a jwt token.

@@ -96,4 +103,4 @@ * The public key used for validation is retrieved from the configured

* ```js
* var claims = {iss: 'name-of-client', sub: 'name-of-client'};
* var options = {privateKey: 'a-private-key'};
* var claims = {iss: 'name-of-client', sub: 'name-of-client', aud: 'name-of-server'};
* var options = {privateKey: 'a-private-key', kid: 'name-of-client/key-id'};
* authenticator.generateToken(claims, options, function (error, token) {

@@ -112,4 +119,8 @@ * if (error) {

* If the subject is generating tokens for itself the `sub` and `iss` should be the same.
* @param {String} claims.aud - Audience. The value that identifies the resource server.
* @param {Object} options
* @param {String} options.privateKey - The private key to use when generating the token.
* @param {String} options.kid - Key ID. The identifier of the key used to sign the token in the format
* 'issuer/key-id' where issuer matches claims.iss.
* @param {Number} [options.expiresInMinutes=0.5] - The number of minutes until the token expires.
* @param {GenerateTokenCallback} callback - The callback that is called when the token has been generated.

@@ -128,4 +139,13 @@ */

var token = jsonWebToken.create(claims, options.privateKey);
callback(null, token);
try {
var token = jsonWebToken.create(claims, {
expiresInMinutes: options.expiresInMinutes,
privateKey: options.privateKey,
kid: options.kid
});
callback(null, token);
} catch (e) {
callback(new Error('Error generating token'));
}
};

@@ -141,8 +161,8 @@

* Generates an authorization header value containing a jwt token.
* The format of the value is `x-atl-jwt [token]`.
* The format of the value is `Bearer [token]`.
*
* @example
* ```js
* var claims = {iss: 'name-of-client', sub: 'name-of-client'};
* var options = {privateKey: 'a-private-key'};
* var claims = {iss: 'name-of-client', sub: 'name-of-client', aud: 'name-of-server'};
* var options = {privateKey: 'a-private-key', kid: 'name-of-client/key-id'};
* authenticator.generateAuthorizationHeader(claims, options, function (error, headerValue) {

@@ -152,3 +172,3 @@ * if (error) {

* } else {
* console.log(headerValue); // -> "x-atl-jwt [token]"
* console.log(headerValue); // -> "Bearer [token]"
* }

@@ -162,4 +182,8 @@ * });

* If the subject is generating tokens for itself the `sub` and `iss` should be the same.
* @param {String} claims.aud - Audience. The value that identifies the resource server.
* @param {Object} options
* @param {String} options.privateKey - The private key to use when generating the token.
* @param {String} options.kid - Key ID. The identifier of the key used to sign the token in the format
* 'issuer/key-id' where issuer matches claims.iss.
* @param {Number} [options.expiresInMinutes=0.5] - The number of minutes until the token expires.
* @param {GenerateAuthorizationHeaderCallback} callback -

@@ -173,3 +197,3 @@ * The callback that is called when the authorization header has been generated.

} else {
callback(null, 'x-atl-jwt ' + token);
callback(null, 'Bearer ' + token);
}

@@ -176,0 +200,0 @@ });

@@ -0,4 +1,5 @@

var _ = require('lodash');
var crypto = require('crypto');
var jsonWebToken = require('jsonwebtoken');
var q = require('q');
var _ = require('lodash');

@@ -20,6 +21,17 @@ module.exports = {

},
create: function (claims, privateKey) {
create: function (claims, options) {
var jsonWebTokenClaims = _.merge({}, claims, {
jti: crypto.randomBytes(20).toString('hex')
});
return jsonWebToken.sign(_.cloneDeep(claims), privateKey, {algorithm: 'RS256'});
var jsonWebTokenOptions = {
algorithm: 'RS256',
expiresInMinutes: options.expiresInMinutes || 0.5,
header: {
kid: options.kid
}
};
return jsonWebToken.sign(jsonWebTokenClaims, options.privateKey, jsonWebTokenOptions);
}
};
{
"name": "jwt-authentication",
"version": "0.0.2",
"version": "0.0.3",
"description": "Library that is used to create and verify json web tokens for service to service authentication purposes.",

@@ -13,3 +13,7 @@ "main": "lib/jwt-authentication.js",

},
"author": "asrinivasan, bsayers",
"contributors": [
{"name": "Ben Sayers", "email": "bsayers@atlassian.com"},
{"name": "David Richard", "email": "drichard@atlassian.com"},
{"name": "Ashwin Srinivasan", "email": "asrinivasan@atlassian.com"}
],
"license": "MIT",

@@ -16,0 +20,0 @@ "devDependencies": {

@@ -20,5 +20,8 @@ # JWT Authentication

* Add custom claims to a token
* Add expiry to tokens
### Server
## !This section is out of date and does not comply with the spec anymore. Validating JWT tokens will fail.!
* Validate a JWT token

@@ -39,4 +42,5 @@ * Automatically retrieve the public key of the issuer of the token

var authenticator = jwtAuthentication.create({publicKeyServer: 'https://public-key-server.com'});
var claims = {iss: 'name-of-client', sub: 'name-of-client'};
authenticator.generateAuthorizationHeader(claims, {privateKey: privateKey}, function (error, headerValue) {
var claims = {iss: 'name-of-client', sub: 'name-of-client', aud: 'name-of-server'};
var options = {privateKey: privateKey, kid: 'name-of-client/key-id'};
authenticator.generateAuthorizationHeader(claims, options, function (error, headerValue) {
if (error) {

@@ -46,3 +50,3 @@ console.log('Generating the token failed.', error);

//assign headerValue to the Authorization header of your request object
console.log(headerValue); // -> "x-atl-jwt [token]"
console.log(headerValue); // -> "Bearer [token]"
}

@@ -70,10 +74,28 @@ });

The public key server is a third party that token receivers trust. The public keys of token creators are published to this server. When the token receiver receives a token it will look at the `iss` claim of the token, retrieve the key for that issuer from the public key server and use it to validate the token.
The public key server is a third party that token receivers trust. The public keys of token creators are published to this server. When the token receiver receives a token it will look at the `kid` claim of the token, retrieve the key for that issuer from the public key server and use it to validate the token.
For example if the following token is sent:
`{"alg": "HS256","typ": "JWT"}.{"iss": "name-of-client", "sub": "name-of-client"}.[signature]`
`{"alg": "HS256", "typ": "JWT", "kid": "name-of-client/key-id"}.{"iss": "name-of-client", "sub": "name-of-client"}.[signature]`
The token receiver will use the public key found at:
`https://public-key-server.com/name-of-client/public.pem`
`https://public-key-server.com/name-of-client/key-id.pem`
## Creating the public and private key pair
### Private Key
```
openssl genrsa -out private.pem 2048
```
This command will generate a private key. This private key must be kept secret, and should be distributed to the token generator in a secure way. Do not commit this key to version control.
### Public Key
```
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
```
This command will generate a public key corresponding to the private key. This key should be uploaded to the public key server so receivers of tokens can validate the token.
## Changelog

@@ -80,0 +102,0 @@

@@ -0,108 +1,205 @@

var _ = require('lodash');
var fs = require('fs');
var jwtSimple = require('jwt-simple');
var jwtAuthentication = require('../../lib/jwt-authentication');
var _ = require('lodash');
var q = require('q');
describe ('jwt-authentication', function () {
// extract 'token' from 'x-atl-jwt token'
var getTokenFromAuthHeader = function(header) {
return header.split(' ')[1];
};
var privateKey = fs.readFileSync('test/integration/key-server/an-issuer/private.pem');
var incorrectPrivateKey = fs.readFileSync('test/integration/key-server/an-issuer/private-wrong.pem');
var createTokenCreator = function() {
return jwtAuthentication.create({publicKeyServer: 'public-key-server-url'});
var createAuthenticator = function (publicKeyServer) {
return jwtAuthentication.create({publicKeyServer: publicKeyServer || 'http://localhost:8000'});
};
var validateJwtToken = function (token, publicKeyName) {
var publicKey = fs.readFileSync('test/integration/key-server/an-issuer/' + publicKeyName + '.pem');
return jwtSimple.decode(token, publicKey);
};
it('should throw error if config is null', function() {
expect(function() {jwtAuthentication.create(null);}).toThrow(
new Error('Required config value config.publicKeyServer is missing.'));
var create = _.partial(jwtAuthentication.create, null);
expect(create).toThrow(new Error('Required config value config.publicKeyServer is missing.'));
});
it('should throw error if config is missing publicKeyServer field', function() {
expect(function() {jwtAuthentication.create({foo: 'bar'});}).toThrow(
new Error('Required config value config.publicKeyServer is missing.'));
var create = _.partial(jwtAuthentication.create, {foo: 'bar'});
expect(create).toThrow(new Error('Required config value config.publicKeyServer is missing.'));
});
describe('generateToken and generateAuthorizationHeader', function () {
var shouldCreateCorrectlySignedToken = function(functionToTest, extractTokenFunction, done) {
var privateKey = fs.readFileSync('test/integration/key-server/an-issuer/private.pem');
createTokenCreator()[functionToTest]({iss: 'an-issuer', sub: 'a-subject', foo: 'abc', bar: 123},
{privateKey: privateKey}, function (error, data) {
expect(error).toBeNull('error');
var token = extractTokenFunction(data);
var decodedJwt = validateJwtToken(token, 'public');
expect(decodedJwt.iss).toBe('an-issuer');
expect(decodedJwt.sub).toBe('a-subject');
expect(decodedJwt.foo).toBe('abc');
expect(decodedJwt.bar).toBe(123);
done();
});
var itShouldGenerateAToken = function (generateToken) {
var validateJwtToken = function (token, publicKeyName) {
var publicKey = fs.readFileSync('test/integration/key-server/an-issuer/' + publicKeyName + '.pem');
return jwtSimple.decode(token, publicKey);
};
it('should create a correctly signed jwt token', function (done) {
shouldCreateCorrectlySignedToken('generateToken', _.identity, done);
shouldCreateCorrectlySignedToken('generateAuthorizationHeader', getTokenFromAuthHeader, done);
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience', foo: 'abc', bar: 123};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeNull('error');
var actualClaims = validateJwtToken(token, 'public');
expect(actualClaims.iss).toBe('an-issuer');
expect(actualClaims.sub).toBe('a-subject');
expect(actualClaims.aud).toBe('an-audience');
expect(actualClaims.foo).toBe('abc');
expect(actualClaims.bar).toBe(123);
done();
});
});
var verifactionShouldFailIfTokenSignedWithWrongKey = function(functionToTest, extractTokenFunction, done) {
var privateKey = fs.readFileSync('test/integration/key-server/an-issuer/private-wrong.pem');
createTokenCreator()[functionToTest]({iss: 'an-issuer', sub: 'a-subject'}, {privateKey: privateKey},
function(error, data) {
expect(error).toBeNull('error');
var token = extractTokenFunction(data);
expect( function() {validateJwtToken(token, 'public');}).toThrow(
new Error('Signature verification failed'));
done();
});
};
it('should contain the kid header in the token', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience', foo: 'abc', bar: 123};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeNull('error');
var segments = token.split('.');
var headerSegment = segments[0];
var header = JSON.parse(new Buffer(headerSegment, 'base64').toString());
expect(header.kid).toBe('path/to/publicKey');
done();
});
});
it('should create tokens with a default expiry of 30 seconds', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeNull('error');
var nowInSeconds = Math.floor(Date.now() / 1000);
var actualClaims = validateJwtToken(token, 'public');
expect(actualClaims.iat).toBeCloseTo(nowInSeconds, 1, 'issued at');
expect(actualClaims.exp).toBe(actualClaims.iat + 30, 'expires');
done();
});
});
it('should allow token expiry to be set', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {expiresInMinutes: 10, kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeNull('error');
var actualClaims = validateJwtToken(token, 'public');
expect(actualClaims.exp).toBe(actualClaims.iat + (60 * 10), 'expires');
done();
});
});
it('should create a signed jwt token that can only be verified with the right public key', function (done) {
verifactionShouldFailIfTokenSignedWithWrongKey('generateToken', _.identity, done);
verifactionShouldFailIfTokenSignedWithWrongKey('generateAuthorizationHeader', getTokenFromAuthHeader, done);
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {privateKey: incorrectPrivateKey, kid: 'path/to/publicKey'};
generateToken(claims, options, function(error, token) {
expect(error).toBeNull('error');
var validate = _.partial(validateJwtToken, token, 'public');
expect(validate).toThrow(new Error('Signature verification failed'));
done();
});
});
var shouldFailIfClaimsIsMissingIssField = function (functionToTest, done) {
var privateKey = fs.readFileSync('test/integration/key-server/an-issuer/private.pem');
createTokenCreator()[functionToTest]({sub: 'a-subject'}, {privateKey: privateKey},
function(error, token) {
expect(error).toEqual(new Error('claims body must have both the "iss" and "sub" fields'));
expect(token).toBeUndefined();
done();
});
};
it('should create tokens with a generated jti claim', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeNull('error');
var actualClaims = validateJwtToken(token, 'public');
expect(actualClaims.jti).toEqual(jasmine.any(String));
done();
});
});
it('should not be trivial to generate the same jti claim twice', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
var generateTokenWithClaims = q.nfbind(generateToken, claims, options);
q.all([generateTokenWithClaims(), generateTokenWithClaims()]).spread(function (token1, token2) {
var claims1 = validateJwtToken(token1, 'public');
var claims2 = validateJwtToken(token2, 'public');
expect(claims1.jti).not.toEqual(claims2.jti);
done();
}).fail(function () {
done('Expected promise to succeed');
});
});
it('should return an error if claims are missing "iss" field', function (done) {
shouldFailIfClaimsIsMissingIssField('generateToken', done);
shouldFailIfClaimsIsMissingIssField('generateAuthorizationHeader', done);
var claims = {sub: 'a-subject', aud: 'an-audience'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeDefined();
expect(error.message).toBe('claims body must contain "iss", "sub" and "aud" fields');
expect(token).toBeUndefined();
done();
});
});
var shouldFailIfClaimsIsMissingSubField = function (functionToTest, done) {
var privateKey = fs.readFileSync('test/integration/key-server/an-issuer/private.pem');
createTokenCreator()[functionToTest]({iss: 'an-issuer'}, {privateKey: privateKey},
function(error, token) {
expect(error).toEqual(new Error('claims body must have both the "iss" and "sub" fields'));
expect(token).toBeUndefined();
done();
});
it('should return an error if claims are missing "sub" field', function (done) {
var claims = {iss: 'an-issuer', aud: 'an-audience'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeDefined();
expect(error.message).toBe('claims body must contain "iss", "sub" and "aud" fields');
expect(token).toBeUndefined();
done();
});
});
it('should return an error if claims are missing "aud" field', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeDefined();
expect(error.message).toBe('claims body must contain "iss", "sub" and "aud" fields');
expect(token).toBeUndefined();
done();
});
});
it('should return an error if options are missing "kid" field', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {privateKey: privateKey};
generateToken(claims, options, function (error, token) {
expect(error).toBeDefined();
expect(error.message).toBe('Options must contain "privateKey" and "kid" fields');
expect(token).toBeUndefined();
done();
});
});
};
describe('generateToken', function () {
var invokeGenerateToken = function (claims, options, callback) {
return createAuthenticator().generateToken(claims, options, callback);
};
it('should return an error if claims are missing "sub" field', function (done) {
shouldFailIfClaimsIsMissingSubField('generateToken', done);
shouldFailIfClaimsIsMissingSubField('generateAuthorizationHeader', done);
itShouldGenerateAToken(invokeGenerateToken);
});
describe('generateAuthorizationHeader', function () {
it('should prefix the token with "Bearer" followed by a space', function (done) {
var claims = {iss: 'an-issuer', sub: 'a-subject', aud: 'an-audience'};
var options = {kid: 'path/to/publicKey', privateKey: privateKey};
createAuthenticator().generateAuthorizationHeader(claims, options, function (error, headerValue) {
expect(error).toBeNull('error');
expect(headerValue.substr(0, 7)).toBe('Bearer ');
done();
});
});
var invokeGenerateAuthorizationHeader = function (claims, options, callback) {
return createAuthenticator().generateAuthorizationHeader(claims, options, function (error, headerValue) {
var token = (headerValue || '').split(' ')[1];
callback(error, token);
});
};
itShouldGenerateAToken(invokeGenerateAuthorizationHeader);
});
describe('validate', function () {
var createValidator = function (publicKeyServer) {
return jwtAuthentication.create({publicKeyServer: publicKeyServer || 'http://localhost:8000'});
};
var createJwtToken = function (privateKeyName, payload) {

@@ -117,3 +214,3 @@ var privateKey = fs.readFileSync('test/integration/key-server/an-issuer/' + privateKeyName + '.pem');

createValidator().validate(jwtToken, function(error, claims) {
createAuthenticator().validate(jwtToken, function(error, claims) {
expect(error).toBeNull('error');

@@ -128,3 +225,3 @@ expect(claims).toEqual({iss: 'an-issuer'}, 'claims');

createValidator('http://localhost:8000/does-not-exist').validate(jwtToken, function (error, claims) {
createAuthenticator('http://localhost:8000/does-not-exist').validate(jwtToken, function (error, claims) {
expect(claims).toBeUndefined('claims');

@@ -145,3 +242,3 @@ expect(error).toBeDefined('error');

createValidator().validate(jwtToken, function(error, claims) {
createAuthenticator().validate(jwtToken, function(error, claims) {
expect(claims).toBeUndefined('claims');

@@ -158,3 +255,3 @@ expect(error).toBeDefined('error');

createValidator().validate(jwtToken, function(error, claims) {
createAuthenticator().validate(jwtToken, function(error, claims) {
expect(claims).toBeUndefined('claims');

@@ -161,0 +258,0 @@ expect(error).toBeDefined('error');

@@ -14,3 +14,3 @@ var q = require('q');

jsonWebToken.verify.andReturn(q());
jsonWebToken.create.andReturn('token');
jsonWebToken.create.andReturn('');

@@ -35,177 +35,231 @@ request = jasmine.createSpy('request');

var checkGenerateTokenHappyPath = function(functionToTest, expectedTokenValue, done) {
validator[functionToTest]({iss: 'iss', sub: 'sub', foo: 'bar'}, {privateKey: 'key'}, function(error, token) {
expect(jsonWebToken.create).toHaveBeenCalledWith({iss: 'iss', sub: 'sub', foo: 'bar'}, 'key');
expect(error).toBeNull();
expect(token).toBe(expectedTokenValue);
done();
var itShouldGenerateAToken = function (methodToTestName) {
var generateToken = function (claims, options, callback) {
return validator[methodToTestName](claims, options, callback);
};
it('should create a jwt token', function (done) {
var claims = {iss: 'iss', sub: 'sub', aud: 'aud', foo: 'bar'};
var options = {kid: 'kid', privateKey: 'key'};
generateToken(claims, options, function () {
var expectedClaims = {iss: 'iss', sub: 'sub', aud: 'aud', foo: 'bar'};
var expectedOptions = {kid: 'kid', privateKey: 'key'};
expect(jsonWebToken.create).toHaveBeenCalledWith(expectedClaims, expectedOptions);
done();
});
});
};
it('should pass arguments to create', function(done) {
checkGenerateTokenHappyPath('generateToken', 'token', done);
checkGenerateTokenHappyPath('generateAuthorizationHeader', 'x-atl-jwt token', done);
});
it('should allow the expiry to be set on the token', function (done) {
var claims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var options = {expiresInMinutes: 10, kid:'kid', privateKey: 'key'};
generateToken(claims, options, function () {
var expectedClaims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var expectedOptions = {expiresInMinutes: 10, kid: 'kid', privateKey: 'key'};
expect(jsonWebToken.create).toHaveBeenCalledWith(expectedClaims, expectedOptions);
done();
});
});
var checkThatErrorIsReturnedWhenOptionsIsNull = function(functionToTest, done) {
validator[functionToTest]({iss: 'iss', sub: 'sub'}, null, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toEqual(new Error('Required value options.privateKey is'));
expect(token).toBeUndefined();
done();
it('should throw an error if options is null', function (done) {
var claims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var options = null;
generateToken(claims, options, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toBeDefined();
expect(error.message).toEqual('Options must contain "privateKey" and "kid" fields');
expect(token).toBeUndefined();
done();
});
});
};
it('should throw an error if options is null', function(done) {
checkThatErrorIsReturnedWhenOptionsIsNull('generateToken', done);
checkThatErrorIsReturnedWhenOptionsIsNull('generateAuthorizationHeader', done);
});
it('should throw an error if options.privateKey is missing', function (done) {
var claims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var options = {kid: 'kid', publicKey: 'key'};
generateToken(claims, options, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toBeDefined();
expect(error.message).toEqual('Options must contain "privateKey" and "kid" fields');
expect(token).toBeUndefined();
done();
});
});
var checkThatErrorIsReturnedWhenPrivateKeyIsMissing = function(functionToTest, done) {
validator[functionToTest]({iss: 'iss', sub: 'sub'}, {publicKey: 'key'}, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toEqual(new Error('Required value options.privateKey is missing'));
expect(token).toBeUndefined();
done();
it('should throw an error if options.kid is missing', function (done) {
var claims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var options = {privateKey: 'key'};
generateToken(claims, options, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toBeDefined();
expect(error.message).toEqual('Options must contain "privateKey" and "kid" fields');
expect(token).toBeUndefined();
done();
});
});
};
it('should throw an error if options.privateKey is missing', function(done) {
checkThatErrorIsReturnedWhenPrivateKeyIsMissing('generateToken', done);
checkThatErrorIsReturnedWhenPrivateKeyIsMissing('generateAuthorizationHeader', done);
});
var itShouldThrowErrorWhenClaimsAreInvalid = function (spec, claims) {
it('should throw an error if ' + spec, function (done) {
var options = {kid: 'kid', privateKey: 'key'};
generateToken(claims, options, function (error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toBeDefined();
expect(error.message).toBe('claims body must contain "iss", "sub" and "aud" fields');
expect(token).toBeUndefined();
done();
});
});
};
var checkThatErrorIsReturnedWhenClaimsIsNull = function(functionToTest, done) {
validator[functionToTest](null, {publicKey: 'key'}, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toEqual(new Error('claims body must have both the "iss" and "sub" fields'));
expect(token).toBeUndefined();
done();
});
};
describe('claims validation', function () {
itShouldThrowErrorWhenClaimsAreInvalid('claims is null', null);
it('should throw an error if claims is null', function(done) {
checkThatErrorIsReturnedWhenClaimsIsNull('generateToken', done);
checkThatErrorIsReturnedWhenClaimsIsNull('generateAuthorizationHeader', done);
});
itShouldThrowErrorWhenClaimsAreInvalid('iss field is missing', {sub: 'sub', aud: 'aud'});
itShouldThrowErrorWhenClaimsAreInvalid('sub field is missing', {iss: 'iss', aud: 'aud'});
itShouldThrowErrorWhenClaimsAreInvalid('aud field is missing', {iss: 'iss', sub: 'sub'});
var checkThatErrorIsReturnedWhenClaimsHasNoIssField = function(functionToTest, done) {
validator[functionToTest]({sub: 'sub'}, {publicKey: 'key'}, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toEqual(new Error('claims body must have both the "iss" and "sub" fields'));
expect(token).toBeUndefined();
done();
itShouldThrowErrorWhenClaimsAreInvalid('iss field is null', {iss: null, sub: 'sub', aud: 'aud'});
itShouldThrowErrorWhenClaimsAreInvalid('sub field is null', {iss: 'iss', sub: null, aud: 'aud'});
itShouldThrowErrorWhenClaimsAreInvalid('aud field is null', {iss: 'iss', sub: 'sub', aud: null});
});
};
it('should throw an error if "iss" field is missing from claims', function(done) {
checkThatErrorIsReturnedWhenClaimsHasNoIssField('generateToken', done);
checkThatErrorIsReturnedWhenClaimsHasNoIssField('generateAuthorizationHeader', done);
});
it('should throw an error if signing the token generated an error', function (done) {
jsonWebToken.create.andCallFake(function () {
throw new Error('error');
});
var checkThatErrorIsReturnedWhenClaimsHasNoSubField = function(functionToTest, done) {
validator[functionToTest]({iss: 'iss'}, {publicKey: 'key'}, function(error, token) {
expect(jsonWebToken.create).not.toHaveBeenCalled();
expect(error).toEqual(new Error('claims body must have both the "iss" and "sub" fields'));
expect(token).toBeUndefined();
done();
var claims = {iss: 'iss', aud: 'aud', sub: 'sub'};
var options = {kid:'kid', privateKey: 'key'};
generateToken(claims, options, function(error, token) {
expect(error).toBeDefined();
expect(error.message).toBe('Error generating token');
expect(token).toBeUndefined();
done();
});
});
};
it('should throw an error if "sub" field is missing from claims', function(done) {
checkThatErrorIsReturnedWhenClaimsHasNoSubField('generateToken', done);
checkThatErrorIsReturnedWhenClaimsHasNoSubField('generateAuthorizationHeader', done);
});
describe('generateToken', function () {
it('should return the generated token', function (done) {
jsonWebToken.create.andReturn('token');
it('should pass the given token to decode', function (done) {
validator.validate('json-web-token', function () {
expect(jsonWebToken.decode).toHaveBeenCalledWith('json-web-token');
done();
var claims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var options = {kid:'kid', privateKey: 'key'};
validator.generateToken(claims, options, function (error, token) {
expect(error).toBeNull();
expect(token).toBe('token');
done();
});
});
itShouldGenerateAToken('generateToken');
});
it('should fetch the public key for the token', function (done) {
jsonWebToken.decode.andReturn({iss: 'an-issuer'});
describe('generateAuthorizationHeader', function () {
it('should return the authorization header containing the token', function (done) {
jsonWebToken.create.andReturn('token');
validator.validate('json-web-token', function () {
expect(request).toHaveBeenCalledWith('http://a-public-key-server/an-issuer/public.pem');
done();
var claims = {iss: 'iss', sub: 'sub', aud: 'aud'};
var options = {kid:'kid', privateKey: 'key'};
validator.generateAuthorizationHeader(claims, options, function (error, headerValue) {
expect(error).toBeNull();
expect(headerValue).toBe('Bearer token');
done();
});
});
itShouldGenerateAToken('generateAuthorizationHeader');
});
it('should verify the token using fetched public key', function (done) {
request.andReturn(q('public-key'));
describe('validate', function () {
it('should pass the given token to decode', function (done) {
validator.validate('json-web-token', function () {
expect(jsonWebToken.decode).toHaveBeenCalledWith('json-web-token');
done();
});
});
validator.validate('json-web-token', function () {
expect(jsonWebToken.verify).toHaveBeenCalledWith('json-web-token', 'public-key');
done();
it('should fetch the public key for the token', function (done) {
jsonWebToken.decode.andReturn({iss: 'an-issuer'});
validator.validate('json-web-token', function () {
expect(request).toHaveBeenCalledWith('http://a-public-key-server/an-issuer/public.pem');
done();
});
});
});
it('should return the verified claims', function (done) {
jsonWebToken.verify.andReturn(q({iss: 'an-issuer'}));
it('should verify the token using fetched public key', function (done) {
request.andReturn(q('public-key'));
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeNull('error');
expect(claims).toEqual({iss: 'an-issuer'}, 'claims');
done();
validator.validate('json-web-token', function () {
expect(jsonWebToken.verify).toHaveBeenCalledWith('json-web-token', 'public-key');
done();
});
});
});
it('should return the error when decoding the claims fails', function (done) {
jsonWebToken.decode.andThrow(new Error('decode failed'));
it('should return the verified claims', function (done) {
jsonWebToken.verify.andReturn(q({iss: 'an-issuer'}));
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('decode failed', 'error.message');
expect(claims).toBeUndefined('claims');
done();
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeNull('error');
expect(claims).toEqual({iss: 'an-issuer'}, 'claims');
done();
});
});
});
it('should return an error when the claims section has no "iss" field', function (done) {
jsonWebToken.decode.andReturn({aToken: 'with-no-iss-field'});
it('should return the error when decoding the claims fails', function (done) {
jsonWebToken.decode.andThrow(new Error('decode failed'));
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('Cannot verify token with no "iss" claim', 'error.message');
expect(claims).toBeUndefined('claims');
done();
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('decode failed', 'error.message');
expect(claims).toBeUndefined('claims');
done();
});
});
});
it('should return the error when fetching the public key fails', function (done) {
request.andReturn(q.reject(new Error('request failed')));
it('should return an error when the claims section has no "iss" field', function (done) {
jsonWebToken.decode.andReturn({aToken: 'with-no-iss-field'});
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('request failed');
expect(claims).toBeUndefined('claims');
done();
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('Cannot verify token with no "iss" claim', 'error.message');
expect(claims).toBeUndefined('claims');
done();
});
});
});
it('should return the error when verifying the token fails', function (done) {
jsonWebToken.verify.andReturn(q.reject(new Error('token verification failed')));
it('should return the error when fetching the public key fails', function (done) {
request.andReturn(q.reject(new Error('request failed')));
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('token verification failed');
expect(claims).toBeUndefined('claims');
done();
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('request failed');
expect(claims).toBeUndefined('claims');
done();
});
});
});
it('should not invoke the callback with errors thrown from the callback', function () {
var callback = jasmine.createSpy('callback').andThrow(new Error('oh no!'));
validator.validate('json-web-token', callback);
it('should return the error when verifying the token fails', function (done) {
jsonWebToken.verify.andReturn(q.reject(new Error('token verification failed')));
waitsFor(function () {
return callback.callCount > 0;
validator.validate('json-web-token', function (error, claims) {
expect(error).toBeDefined('error');
expect(error.message).toBe('token verification failed');
expect(claims).toBeUndefined('claims');
done();
});
});
runs(function () {
expect(callback.callCount).toBe(1);
it('should not invoke the callback with errors thrown from the callback', function () {
var callback = jasmine.createSpy('callback').andThrow(new Error('oh no!'));
validator.validate('json-web-token', callback);
waitsFor(function () {
return callback.callCount > 0;
});
runs(function () {
expect(callback.callCount).toBe(1);
});
});
});
});

@@ -6,2 +6,3 @@ var specHelpers = require('../support/spec-helpers');

describe('jwt-authentication/json-web-token', function () {
var crypto;
var jsonWebTokenClaims;

@@ -12,2 +13,7 @@ var jsonWebToken;

beforeEach(function () {
crypto = jasmine.createSpyObj('crypto', ['randomBytes']);
crypto.randomBytes.andCallFake(function () {
return new Buffer('');
});
jsonWebTokenClaims = {};

@@ -20,3 +26,4 @@ jsonWebToken = jasmine.createSpyObj('jsonWebToken', ['decode', 'verify', 'sign']);

jwtPromiseWrapper = specHelpers.requireWithMocks('jwt-authentication/json-web-token', {
'jsonwebtoken': jsonWebToken
'jsonwebtoken': jsonWebToken,
'crypto': crypto
});

@@ -27,16 +34,80 @@ });

it('should pass through the arguments to jsonWebToken.sign', function() {
jwtPromiseWrapper.create({iss: 'issuer', sub: 'subject'}, 'private-key');
jwtPromiseWrapper.create({iss: 'issuer', sub: 'subject'}, {kid: 'a-kid', privateKey: 'private-key'});
expect(jsonWebToken.sign).toHaveBeenCalledWith(
{iss: 'issuer', sub: 'subject'},
{iss: 'issuer', sub: 'subject', jti: jasmine.any(String)},
'private-key',
{algorithm: 'RS256'});
{algorithm: 'RS256', expiresInMinutes: 0.5, header: {kid: 'a-kid'}});
});
it('should allow expiresInMinutes to be set', function () {
var claims = {iss: 'issuer', sub: 'subject'};
var options = {expiresInMinutes: 10, privateKey: 'private-key'};
jwtPromiseWrapper.create(claims, options);
expect(jsonWebToken.sign).toHaveBeenCalledWith(
jasmine.any(Object),
jasmine.any(String),
jasmine.objectContaining({expiresInMinutes: 10})
);
});
it('should pass through additional claims if they are passed in', function() {
jwtPromiseWrapper.create({iss: 'issuer', sub: 'subject', claim1: 'foo', claim2: 'bar'}, 'private-key');
jwtPromiseWrapper.create(
{iss: 'issuer', sub: 'subject', claim1: 'foo', claim2: 'bar'},
{privateKey: 'private-key'}
);
expect(jsonWebToken.sign).toHaveBeenCalledWith(
{iss: 'issuer', sub: 'subject', claim1: 'foo', claim2: 'bar'},
'private-key',
{algorithm: 'RS256'});
{iss: 'issuer', sub: 'subject', jti: jasmine.any(String), claim1: 'foo', claim2: 'bar'},
jasmine.any(String),
jasmine.any(Object));
});
it('should pass through the kid as a header option', function () {
var claims = {iss: 'issuer', sub: 'subject'};
var options = {kid: 'a-kid', privateKey: 'private-key'};
jwtPromiseWrapper.create(claims, options);
expect(jsonWebToken.sign).toHaveBeenCalledWith(
jasmine.any(Object),
jasmine.any(String),
jasmine.objectContaining({
header: {
kid: 'a-kid'
}
})
);
});
it('should generate random jti claim and include it in the token', function () {
crypto.randomBytes.andCallFake(function () {
return new Buffer('20-random-bytes');
});
var twentyRandomBytesInHex = '32302d72616e646f6d2d6279746573';
var claims = {iss: 'issuer', sub: 'subject'};
var options = {kid: 'a-kid', privateKey: 'private-key'};
jwtPromiseWrapper.create(claims, options);
expect(crypto.randomBytes).toHaveBeenCalledWith(20);
expect(jsonWebToken.sign).toHaveBeenCalledWith(
jasmine.objectContaining({
jti: twentyRandomBytesInHex
}),
jasmine.any(String),
jasmine.any(Object)
);
});
it('should not be possible to override the jti claim', function () {
var claims = {jti: ['not', 'a string']};
var options = {kid: 'a-kid', privateKey: 'private-key'};
jwtPromiseWrapper.create(claims, options);
expect(jsonWebToken.sign).toHaveBeenCalledWith(
jasmine.objectContaining({
jti: jasmine.any(String)
}),
jasmine.any(String),
jasmine.any(Object)
);
});
});

@@ -43,0 +114,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc