jwt-authentication
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63663
969
135
1