Skip to content

Commit

Permalink
Merge pull request #146 from bitcloud/disable_default_urlkey
Browse files Browse the repository at this point in the history
added parameter to irgnore url token
  • Loading branch information
nelsonic committed Apr 30, 2016
2 parents fe07f63 + 212a8c4 commit 8da91d7
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 38 deletions.
62 changes: 31 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ This node.js module (Hapi plugin) lets you use JSON Web Tokens (JWTs)
for authentication in your [Hapi.js](http://hapijs.com/)
web application.

If you are totally new to JWTs, we wrote an introductory post explaining
If you are totally new to JWTs, we wrote an introductory post explaining
the concepts & benefits: https://github.com/dwyl/learn-json-web-tokens

If you (or anyone on your team) are unfamiliar with **Hapi.js** we have a
If you (or anyone on your team) are unfamiliar with **Hapi.js** we have a
quick guide for that too: https://github.com/dwyl/learn-hapi

## Usage
Expand Down Expand Up @@ -172,7 +172,7 @@ signature `function(decoded, callback)` where:
- `validateFunc` - (***required***) the function which is run once the Token has been decoded with
signature `function(decoded, request, callback)` where:
- `decoded` - (***required***) is the ***decoded*** and ***verified*** JWT received from the client in **request.headers.authorization**
- `request` - (***required***) is the original ***request*** received from the client
- `request` - (***required***) is the original ***request*** received from the client
- `callback` - (***required***) a callback function with the signature `function(err, isValid, credentials)` where:
- `err` - an internal error.
- `valid` - `true` if the JWT was valid, otherwise `false`.
Expand All @@ -189,10 +189,10 @@ signature `function(decoded, callback)` where:
- `responseFunc` - (***optional***) function called to decorate the response with authentication headers before the response headers or payload is written where:
- `request` - the request object.
- `reply(err, response)`- is called if an error occurred
- `urlKey` - (***optional*** *defaults to* `'token'`) - if you prefer to pass your token via url, simply add a `token` url parameter to your request or use a custom parameter by setting `urlKey`
- `cookieKey` - (***optional*** *defaults to* `'token'`) if you prefer to set your own cookie key or your project has a cookie called `'token'` for another purpose, you can set a custom key for your cookie by setting `options.cookieKey='yourkeyhere'`.
- `headerKey` - (***optional*** *defaults to* `'authorization'`) - if you want to set a custom key for your header token use the `headerKey` option.
- `tokenType` - (***optional*** *defaults to noe*) allow custom token type, e.g. `Authorization: <tokenType> 12345678`.
- `urlKey` - (***optional*** *defaults to* `'token'`) - if you prefer to pass your token via url, simply add a `token` url parameter to your request or use a custom parameter by setting `urlKey`. To disable the url parameter set urlKey to `false` or ''.
- `cookieKey` - (***optional*** *defaults to* `'token'`) - if you prefer to set your own cookie key or your project has a cookie called `'token'` for another purpose, you can set a custom key for your cookie by setting `options.cookieKey='yourkeyhere'`. To disable cookies set cookieKey to `false` or ''.
- `headerKey` - (***optional*** *defaults to* `'authorization'`) - if you want to set a custom key for your header token use the `headerKey` option. To disable header token set headerKey to `false` or ''.
- `tokenType` - (***optional*** *defaults to none*) - allow custom token type, e.g. `Authorization: <tokenType> 12345678`.
- `complete` - (***optional*** *defaults to* `false`) - set to `true` to receive the complete token (`decoded.header`, `decoded.payload` and `decoded.signature`) as `decoded` argument to key lookup and `verifyFunc` callbacks (*not `validateFunc`*)


Expand Down Expand Up @@ -288,20 +288,20 @@ var url = "/path?token="+token;
```

> What if I want to *disable* the ability to pass JWTs in via the URL?
(*asked by* @bitcloud in [issue #146](https://github.com/dwyl/hapi-auth-jwt2/pull/146))
(*asked by* @bitcloud in [issue #146](https://github.com/dwyl/hapi-auth-jwt2/pull/146))
> simply set your `urlKey` to something *impossible* to guess see:
[*example*](https://github.com/dwyl/hapi-auth-jwt2/pull/146#issuecomment-205481751)

## Generating Your Secret Key

@skota asked "***How to generate secret key***?" in: [dwyl/hapi-auth-jwt2/issues/**48**](https://github.com/dwyl/hapi-auth-jwt2/issues/48)

There are _several_ options for generating secret keys.
There are _several_ options for generating secret keys.
The _easist_ way is to run node's crypto hash in your terminal:
```js
node -e "console.log(require('crypto').randomBytes(256).toString('base64'));"
```
and copy the resulting base64 key and use it as your JWT secret.
and copy the resulting base64 key and use it as your JWT secret.
If you are *curious* how strong that key is watch: https://youtu.be/koJQQWHI-ZA


Expand All @@ -316,15 +316,15 @@ within the request lifecycle with the `request.auth.token` property.

*Note* that this is the ***encoded token***,
and it's only useful if you want to use to make
request to other servers using the user's token.
request to other servers using the user's token.

The *decoded* version of the token, accessible via `request.auth.credentials`

## Want to send/store your JWT in a Cookie?

[@benjaminlees](https://github.com/benjaminlees)
requested the ability to send/receive tokens as cookies:
[dwyl/hapi-auth-jwt2/issues/**55**](https://github.com/dwyl/hapi-auth-jwt2/issues/55)
[dwyl/hapi-auth-jwt2/issues/**55**](https://github.com/dwyl/hapi-auth-jwt2/issues/55)
So we added the ability to *optionally* send/store your tokens in cookies
to simplify building your *web app*.

Expand Down Expand Up @@ -371,29 +371,29 @@ http://tools.ietf.org/html/rfc6265

## Frequently Asked Questions (FAQ)

1. Do I need to include **jsonwebtoken** in my project? asked in [hapi-auth-jwt2/issues/32](https://github.com/dwyl/hapi-auth-jwt2/issues/32)
1. Do I need to include **jsonwebtoken** in my project? asked in [hapi-auth-jwt2/issues/32](https://github.com/dwyl/hapi-auth-jwt2/issues/32)
**Q**: Must I include the **jsonwebtoken** package in my project
[given that **hapi-auth-jwt2** plugin already includes it] ?
[given that **hapi-auth-jwt2** plugin already includes it] ?
**A**: Yes, you need to *manually* install the **jsonwebtoken**
node module from NPM with `npm install jsonwebtoken --save` if you want to ***sign*** JWTs in your app.
node module from NPM with `npm install jsonwebtoken --save` if you want to ***sign*** JWTs in your app.
Even though **hapi-auth-jwt2** includes it
as a **dependency** your app does not know where to find it in the **node_modules** tree for your project.
as a **dependency** your app does not know where to find it in the **node_modules** tree for your project.
Unless you include it via ***relative path*** e.g:
`var JWT = require('./node_modules/hapi-auth-jwt2/node_modules/jsonwebtoken');`
`var JWT = require('./node_modules/hapi-auth-jwt2/node_modules/jsonwebtoken');`
we *recommend* including it in your **package.json** ***explicitly*** as a **dependency** for your project.

2. Can we supply a ***Custom Verification*** function instead of using the **JWT.verify** method?
2. Can we supply a ***Custom Verification*** function instead of using the **JWT.verify** method?
asked by *both* [Marcus Stong](https://github.com/stongo) & [Kevin Stewart](https://github.com/kdstew)
in [issue #120](https://github.com/dwyl/hapi-auth-jwt2/issues/120) and [issue #130](https://github.com/dwyl/hapi-auth-jwt2/issues/130) respectively.
**Q**: Does this module support custom verification function or disabling verification?
**A**: Yes, it *does now*! (*see: "Advanced Usage" below*) the inclusion of a `verifyFunc`
in [issue #120](https://github.com/dwyl/hapi-auth-jwt2/issues/120) and [issue #130](https://github.com/dwyl/hapi-auth-jwt2/issues/130) respectively.
**Q**: Does this module support custom verification function or disabling verification?
**A**: Yes, it *does now*! (*see: "Advanced Usage" below*) the inclusion of a `verifyFunc`
gives you *complete control* over the verification of the incoming JWT.


## *Advanced/Alternative* Usage => Bring Your Own `verifyFunc`

While *most* people using `hapi-auth-jwt2` will opt for the *simpler* use case
(*using a* ***Validation Function*** *`validateFunc` - see: Basic Usage example above -
(*using a* ***Validation Function*** *`validateFunc` - see: Basic Usage example above -
which validates the JWT payload after it has been verified...*)
others may need more control over the `verify` step.

Expand All @@ -407,19 +407,19 @@ while you are initializing the plugin.
- `verifyFunc` - (***optional***) the function which is run once the Token has been decoded
(*instead of a `validateFunc`*) with signature `function(decoded, request, callback)` where:
- `decoded` - (***required***) is the ***decoded*** and ***verified*** JWT received from the client in **request.headers.authorization**
- `request` - (***required***) is the original ***request*** received from the client
- `request` - (***required***) is the original ***request*** received from the client
- `callback` - (***required***) a callback function with the signature `function(err, isValid, credentials)` where:
- `err` - an internal error.
- `valid` - `true` if the JWT was valid, otherwise `false`.
- `credentials` - (***optional***) alternative credentials to be set instead of `decoded`.

The advantage of this approach is that it allows people to write a
custom verification function or to bypass the `JWT.verify` *completely*.
custom verification function or to bypass the `JWT.verify` *completely*.
For more detail, see: use-case discussion in https://github.com/dwyl/hapi-auth-jwt2/issues/120


> ***Note***: *nobody has requested the ability to use* ***both*** *a*
`validateFunc` ***and*** `verifyFunc`.
`validateFunc` ***and*** `verifyFunc`.
This should not be *necessary*
because with a `verifyFunc` you can incorporate your own custom-logic.

Expand All @@ -436,7 +436,7 @@ See the release notes for more details:
However in the interest of
security/performance we *recommend* using the [*latest version*](https://github.com/hapijs/hapi/) of Hapi.

> *If you have a question, or need help getting started* ***please post an issue/question on
> *If you have a question, or need help getting started* ***please post an issue/question on
GitHub***: https://github.com/dwyl/hapi-auth-jwt2/issues *or*
[![Join the chat at https://gitter.im/dwyl/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dwyl/chat/?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Expand Down Expand Up @@ -482,7 +482,7 @@ if the [session record is ***found*** (valid) and ***not ended***](https://githu
[api/lib/auth_jwt_sign.js](https://github.com/dwyl/time/blob/0a5ec8711840528a4960c388825fb883fabddd76/api/lib/auth_jwt_sign.js#L18)

If you have ***any questions*** on this please post an issue/question on GitHub:
https://github.com/dwyl/hapi-auth-jwt2/issues
https://github.com/dwyl/hapi-auth-jwt2/issues
(*we are here to help get you started on your journey to **hapi**ness!*)

<br />
Expand All @@ -491,7 +491,7 @@ https://github.com/dwyl/hapi-auth-jwt2/issues

## Contributing [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/hapi-auth-jwt2/issues)

If you spot an area for improvement, please raise an issue: https://github.com/dwyl/hapi-auth-jwt2/issues
If you spot an area for improvement, please raise an issue: https://github.com/dwyl/hapi-auth-jwt2/issues
*Someone* in the dwyl team is *always* online so we will usually answer within a few hours.

### Running the tests requires environment variables
Expand All @@ -512,15 +512,15 @@ export REDISCLOUD_URL='redis://rediscloud:[email protected]
## Motivation

While making [***Time***](https://github.com/dwyl/time) we want to ensure
our app (and API) is as ***simple*** as *possible* to use.
our app (and API) is as ***simple*** as *possible* to use.
This lead us to using JSON Web Tokens for ***Stateless*** Authentication.

We did a *extensive* [research](https://www.npmjs.com/search?q=hapi+auth+jwt)
into *existing* modules that *might* solve our problem; there are *many* on NPM:
![npm search for hapi+jwt](http://i.imgur.com/xIj3Xpa.png)

but they were invariably ***too complicated***, poorly documented and
had *useless* (non-real-world) "examples"!
had *useless* (non-real-world) "examples"!

Also, none of the *existing* modules exposed the **request** object
to the **validateFunc** which we thought might be handy.
Expand All @@ -532,7 +532,7 @@ So we decided to write our own module addressing all these issues.

## Why hapi-auth-jwt2 ?

The name we wanted was taken.
The name we wanted was taken.
Think of our module as the "***new, simplified and actively maintained version***"

## Useful Links
Expand Down
13 changes: 6 additions & 7 deletions lib/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,24 @@ var Cookie = require('cookie'); // highly popular decoupled cookie parser
*/

module.exports = function (request, options) {

// The key holding token value in url or cookie defaults to token
var urlKey = typeof options.urlKey === 'string' ? options.urlKey : 'token';
var cookieKey = typeof options.cookieKey === 'string' ? options.cookieKey : 'token';
var headerKey = typeof options.headerKey === 'string' ? options.headerKey : 'authorization';
var urlKey = options.urlKey === false || typeof options.urlKey === 'string' ? options.urlKey : 'token';
var cookieKey = options.cookieKey === false || typeof options.cookieKey === 'string' ? options.cookieKey : 'token';
var headerKey = options.headerKey === false || typeof options.headerKey === 'string' ? options.headerKey : 'authorization';
var auth;

if(request.query[urlKey]) { // tokens via url: https://github.com/dwyl/hapi-auth-jwt2/issues/19
if(urlKey && request.query[urlKey]) { // tokens via url: https://github.com/dwyl/hapi-auth-jwt2/issues/19
auth = request.query[urlKey];
} // JWT tokens in cookie: https://github.com/dwyl/hapi-auth-jwt2/issues/55
else if (request.headers[headerKey]) {
else if (headerKey && request.headers[headerKey]) {
if (typeof options.tokenType === 'string') {
var token = request.headers[headerKey].match(new RegExp(options.tokenType + '\\s+([^$]+)', 'i'));
auth = token === null ? null : token[1];
} else {
auth = request.headers[headerKey];
}
}
else if (request.headers.cookie) {
else if (cookieKey && request.headers.cookie) {
auth = Cookie.parse(request.headers.cookie)[cookieKey];
}

Expand Down
32 changes: 32 additions & 0 deletions test/basic_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,42 @@ server.register(require('../'), function () {
verifyOptions: { algorithms: [ 'HS256' ] } // only allow HS256 algorithm
});

server.auth.strategy('jwt-nourl', 'jwt', {
key: secret,
validateFunc: validate,
verifyOptions: { algorithms: [ 'HS256' ] }, // only allow HS256 algorithm
urlKey: false
});

server.auth.strategy('jwt-nocookie', 'jwt', {
key: secret,
validateFunc: validate,
verifyOptions: { algorithms: [ 'HS256' ] }, // only allow HS256 algorithm
cookieKey: false
});

server.auth.strategy('jwt-nourl2', 'jwt', {
key: secret,
validateFunc: validate,
verifyOptions: { algorithms: [ 'HS256' ] }, // only allow HS256 algorithm
urlKey: ''
});

server.auth.strategy('jwt-nocookie2', 'jwt', {
key: secret,
validateFunc: validate,
verifyOptions: { algorithms: [ 'HS256' ] }, // only allow HS256 algorithm
cookieKey: ''
});

server.route([
{ method: 'GET', path: '/', handler: home, config: { auth: false } },
{ method: 'GET', path: '/token', handler: sendToken, config: { auth: 'jwt' } },
{ method: 'POST', path: '/privado', handler: privado, config: { auth: 'jwt' } },
{ method: 'POST', path: '/privadonourl', handler: privado, config: { auth: 'jwt-nourl' } },
{ method: 'POST', path: '/privadonocookie', handler: privado, config: { auth: 'jwt-nocookie' } },
{ method: 'POST', path: '/privadonourl2', handler: privado, config: { auth: 'jwt-nourl2' } },
{ method: 'POST', path: '/privadonocookie2', handler: privado, config: { auth: 'jwt-nocookie2' } },
{ method: 'POST', path: '/required', handler: privado, config: { auth: { mode: 'required', strategy: 'jwt' } } },
{ method: 'POST', path: '/optional', handler: privado, config: { auth: { mode: 'optional', strategy: 'jwt' } } },
{ method: 'POST', path: '/try', handler: privado, config: { auth: { mode: 'try', strategy: 'jwt' } } }
Expand Down
30 changes: 30 additions & 0 deletions test/cookies_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,33 @@ test("Valid Google Analytics cookie should be ignored (BAD Header Token)", funct
t.end();
});
});

test("Attempt to access restricted content with cookieKey=false", function(t) {
var token = JWT.sign({ id: 123, "name": "Charlie" }, secret);
var options = {
method: "POST",
url: "/privadonocookie",
headers: { cookie: "token=" + token }
};
// server.inject lets us simulate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "Disabled cookie auth shouldn't accept valid token!");
t.end();
});
});


test("Attempt to access restricted content with cookieKey=''", function(t) {
var token = JWT.sign({ id: 123, "name": "Charlie" }, secret);
var options = {
method: "POST",
url: "/privadonocookie2",
headers: { cookie: "=" + token }
};
// server.inject lets us simulate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 400, "Disabled cookie auth shouldn't accept valid token!");
t.end();
});
});

30 changes: 30 additions & 0 deletions test/url_token_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,33 @@ test("Access restricted content (with VALID Token)", function(t) {
t.end();
});
});

test("Using route with urlKey=false should be denied", function(t) {
// use the token as the 'authorization' header in requests
var token = JWT.sign({ id: 123, "name": "Charlie" }, secret);
token = "?token=" + token;
var options = {
method: "POST",
url: "/privadonourl" + token
};
// server.inject lets us simulate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "No urlKey configured so URL-Tokens should be denied");
t.end();
});
});

test("Using route with urlKey='' should be denied", function(t) {
// use the token as the 'authorization' header in requests
var token = JWT.sign({ id: 123, "name": "Charlie" }, secret);
token = "?=" + token;
var options = {
method: "POST",
url: "/privadonourl2" + token
};
// server.inject lets us simulate an http request
server.inject(options, function(response) {
t.equal(response.statusCode, 401, "No urlKey configured so URL-Tokens should be denied");
t.end();
});
});

0 comments on commit 8da91d7

Please sign in to comment.