hapi-auth-jwt2
Advanced tools
Comparing version 8.6.2 to 8.7.0
'use strict'; | ||
const Cookie = require('cookie'); // highly popular decoupled cookie parser | ||
const internals = {}; // see: http://hapijs.com/styleguide#module-globals | ||
@@ -32,2 +33,3 @@ /** | ||
const urlKey = customOrDefaultKey(options, 'urlKey', 'token'); | ||
const payloadKey = customOrDefaultKey(options, 'payloadKey', 'token'); | ||
const pattern = new RegExp(options.tokenType + '\\s+([^$]+)', 'i'); | ||
@@ -48,5 +50,22 @@ | ||
} | ||
if (payloadKey && request.payload && request.payload[payloadKey]) { | ||
auth = request.payload[payloadKey]; | ||
} | ||
if (!auth && options.customExtractionFunc) { | ||
auth = options.customExtractionFunc(request); | ||
} | ||
// strip pointless "Bearer " label & any whitespace > http://git.io/xP4F | ||
return auth ? auth.replace(/Bearer/gi, '').replace(/ /g, '') : null; | ||
auth = auth ? auth.replace(/Bearer/gi, '').replace(/ /g, '') : null; | ||
// If we are receiving a headerless JWT token let reconstruct it using the custom function | ||
if ( | ||
options.headless && | ||
typeof options.headless === 'object' && | ||
internals.isHeadless(auth) | ||
) { | ||
auth = `${Buffer.from(JSON.stringify(options.headless)).toString( | ||
'base64' | ||
)}.${auth}`; | ||
} | ||
return auth; | ||
}; | ||
@@ -73,4 +92,4 @@ | ||
*/ | ||
module.exports.isHeadless = function isHeadless(token) { | ||
return token.split('.').length === 2; | ||
internals.isHeadless = function isHeadless(token) { | ||
return token && token.split('.').length === 2; | ||
}; |
@@ -105,2 +105,10 @@ import { Request, ResponseObject, Plugin, ResponseToolkit } from '@hapi/hapi'; | ||
/** | ||
* If you want to set a custom key for your payload token use the | ||
* `payloadKey` option. To disable payload token set payloadKey to `false` or | ||
* ''. | ||
* @default 'token' | ||
*/ | ||
payloadKey?: string | boolean; | ||
/** | ||
* Allow custom token type, e.g. `Authorization: <tokenType> 12345678` | ||
@@ -117,2 +125,16 @@ */ | ||
complete?: boolean; | ||
/** | ||
* Set to `true` to allow the `payloadFunc` to attempt to extract the token from | ||
* POST bodies | ||
* @default false | ||
*/ | ||
attemptToExtractTokenInPayload?: boolean; | ||
/** | ||
* Custom token extraction function used to allow consumers to pull tokens from | ||
* sources not foreseen by the module, for example... YAR | ||
* @default false | ||
*/ | ||
customExtractionFunc?(request: Request): string; | ||
} | ||
@@ -119,0 +141,0 @@ |
361
lib/index.js
@@ -6,3 +6,3 @@ 'use strict'; | ||
const JWT = require('jsonwebtoken'); // https://github.com/docdis/learn-json-web-tokens | ||
const extract = require('./extract'); // extract token from Auth Header, URL or Coookie | ||
const extract = require('./extract'); // extract token from Auth Header, URL or Cookie | ||
const pkg = require('../package.json'); // use package name and version rom package.json | ||
@@ -40,2 +40,4 @@ const internals = {}; // see: http://hapijs.com/styleguide#module-globals | ||
internals.FIRST_PASS_AUTHENTICATION_FAILED = 'firstPassAuthenticationFailed'; | ||
/** | ||
@@ -85,2 +87,191 @@ * checkObjectType returns the class of the object's prototype | ||
internals.authenticate = async function(token, options, request, h) { | ||
let tokenType = options.tokenType || 'Token'; // see: https://git.io/vXje9 | ||
let decoded; | ||
if (!token && request.auth.mode === 'optional') { | ||
return { | ||
error: internals.raiseError(options, 'unauthorized', null, tokenType), | ||
payload: { | ||
credentials: tokenType, | ||
}, | ||
}; | ||
} | ||
if (!token) { | ||
return { | ||
error: internals.raiseError( | ||
options, | ||
'unauthorized', | ||
'token is null', | ||
tokenType | ||
), | ||
payload: { | ||
credentials: tokenType, | ||
}, | ||
}; | ||
} | ||
// quick check for validity of token format | ||
if (!extract.isValid(token)) { | ||
return { | ||
error: internals.raiseError( | ||
options, | ||
'unauthorized', | ||
'Invalid token format', | ||
tokenType | ||
), | ||
payload: { | ||
credentials: token, | ||
}, | ||
}; | ||
} | ||
// verification is done later, but we want to avoid decoding if malformed | ||
request.auth.token = token; // keep encoded JWT available in the request | ||
// otherwise use the same key (String) to validate all JWTs | ||
try { | ||
decoded = JWT.decode(token, { complete: options.complete || false }); | ||
} catch (e) { | ||
return { | ||
error: internals.raiseError( | ||
options, | ||
'unauthorized', | ||
'Invalid token format', | ||
tokenType | ||
), | ||
payload: { | ||
credentials: token, | ||
}, | ||
}; | ||
} | ||
if (typeof options.validate === 'function') { | ||
const { keys, extraInfo } = await internals.getKeys(decoded, options); | ||
/* istanbul ignore else */ | ||
if (extraInfo) { | ||
request.plugins[pkg.name] = { extraInfo }; | ||
} | ||
let verify_decoded; | ||
try { | ||
verify_decoded = internals.verifyJwt(token, keys, options); | ||
} catch (verify_err) { | ||
let err_message = | ||
verify_err.message === 'jwt expired' | ||
? 'Expired token' | ||
: 'Invalid token'; | ||
return { | ||
error: internals.raiseError( | ||
options, | ||
'unauthorized', | ||
err_message, | ||
tokenType | ||
), | ||
payload: { credentials: token }, | ||
}; | ||
} | ||
try { | ||
let { | ||
isValid, | ||
credentials, | ||
response, | ||
errorMessage, | ||
} = await options.validate(verify_decoded, request, h); | ||
if (response !== undefined) { | ||
return { response }; | ||
} | ||
if (!isValid) { | ||
// invalid credentials | ||
return { | ||
error: internals.raiseError( | ||
options, | ||
'unauthorized', | ||
errorMessage || 'Invalid credentials', | ||
tokenType | ||
), | ||
payload: { credentials: decoded }, | ||
}; | ||
} | ||
// valid key and credentials | ||
return { | ||
payload: { | ||
credentials: | ||
credentials && typeof credentials === 'object' | ||
? credentials | ||
: decoded, | ||
artifacts: token, | ||
}, | ||
}; | ||
} catch (validate_err) { | ||
return { | ||
error: internals.raiseError(options, 'boomify', validate_err), | ||
payload: { | ||
credentials: decoded, | ||
}, | ||
}; | ||
} | ||
} | ||
// see: https://github.com/dwyl/hapi-auth-jwt2/issues/130 | ||
try { | ||
let { isValid, credentials } = await options.verify(decoded, request); | ||
if (!isValid) { | ||
return { | ||
error: internals.raiseError( | ||
options, | ||
'unauthorized', | ||
'Invalid credentials', | ||
tokenType | ||
), | ||
payload: { credentials: decoded }, | ||
}; | ||
} | ||
return { | ||
payload: { | ||
credentials: credentials, | ||
artifacts: token, | ||
}, | ||
}; | ||
} catch (verify_error) { | ||
return { | ||
error: internals.raiseError(options, 'boomify', verify_error), | ||
payload: { | ||
credentials: decoded, | ||
}, | ||
}; | ||
} | ||
}; | ||
// allow custom error raising or default to Boom if no errorFunc is defined | ||
internals.raiseError = function raiseError( | ||
options, | ||
errorType, | ||
message, | ||
scheme, | ||
attributes | ||
) { | ||
let errorContext = { | ||
errorType: errorType, | ||
message: message, | ||
scheme: scheme, | ||
attributes: attributes, | ||
}; | ||
if (internals.isFunction(options.errorFunc)) { | ||
errorContext = options.errorFunc(errorContext); | ||
} | ||
// Since it is clearly specified in the docs that | ||
// the errorFunc must return an object with keys: | ||
// errorType and message, we need not worry about | ||
// errorContext being undefined | ||
return Boom[errorContext.errorType]( | ||
errorContext.message, | ||
errorContext.scheme, | ||
errorContext.attributes | ||
); | ||
}; | ||
/** | ||
@@ -102,26 +293,2 @@ * implementation is the "main" interface to the plugin and contains all the | ||
// allow custom error raising or default to Boom if no errorFunc is defined | ||
function raiseError(errorType, message, scheme, attributes) { | ||
let errorContext = { | ||
errorType: errorType, | ||
message: message, | ||
scheme: scheme, | ||
attributes: attributes, | ||
}; | ||
if (internals.isFunction(options.errorFunc)) { | ||
errorContext = options.errorFunc(errorContext); | ||
} | ||
// Since it is clearly speacified in the docs that | ||
// the errorFunc must return an object with keys: | ||
// errorType and message, we need not worry about | ||
// errorContext being undefined | ||
return Boom[errorContext.errorType]( | ||
errorContext.message, | ||
errorContext.scheme, | ||
errorContext.attributes | ||
); | ||
} | ||
return { | ||
@@ -138,117 +305,21 @@ /** | ||
let token = extract(request, options); // extract token Header/Cookie/Query | ||
let tokenType = options.tokenType || 'Token'; // see: https://git.io/vXje9 | ||
let decoded; | ||
if (!token) { | ||
return h.unauthenticated(raiseError('unauthorized', null, tokenType), { | ||
credentials: tokenType, | ||
}); | ||
} | ||
// If we are receiving a headerless JWT token let reconstruct it using the custom function | ||
if ( | ||
options.headless && | ||
typeof options.headless === 'object' && | ||
extract.isHeadless(token) | ||
token == null && | ||
options.attemptToExtractTokenInPayload && | ||
request.method.toLowerCase() === 'post' | ||
) { | ||
token = `${Buffer.from(JSON.stringify(options.headless)).toString( | ||
'base64' | ||
)}.${token}`; | ||
} | ||
// quick check for validity of token format | ||
if (!extract.isValid(token)) { | ||
return h.unauthenticated( | ||
raiseError('unauthorized', 'Invalid token format', tokenType), | ||
{ credentials: token } | ||
); | ||
} | ||
// verification is done later, but we want to avoid decoding if malformed | ||
request.auth.token = token; // keep encoded JWT available in the request | ||
// otherwise use the same key (String) to validate all JWTs | ||
try { | ||
decoded = JWT.decode(token, { complete: options.complete || false }); | ||
} catch (e) { | ||
return h.unauthenticated( | ||
raiseError('unauthorized', 'Invalid token format', tokenType), | ||
{ credentials: token } | ||
); | ||
} | ||
if (typeof options.validate === 'function') { | ||
const { keys, extraInfo } = await internals.getKeys(decoded, options); | ||
/* istanbul ignore else */ | ||
if (extraInfo) { | ||
request.plugins[pkg.name] = { extraInfo }; | ||
} | ||
let verify_decoded; | ||
try { | ||
verify_decoded = internals.verifyJwt(token, keys, options); | ||
} catch (verify_err) { | ||
let err_message = | ||
verify_err.message === 'jwt expired' | ||
? 'Expired token' | ||
: 'Invalid token'; | ||
return h.unauthenticated( | ||
raiseError('unauthorized', err_message, tokenType), | ||
{ credentials: token } | ||
); | ||
} | ||
try { | ||
let { | ||
isValid, | ||
credentials, | ||
response, | ||
errorMessage, | ||
} = await options.validate(verify_decoded, request, h); | ||
if (response !== undefined) { | ||
return h.response(response).takeover(); | ||
} | ||
if (!isValid) { | ||
// invalid credentials | ||
return h.unauthenticated( | ||
raiseError( | ||
'unauthorized', | ||
errorMessage || 'Invalid credentials', | ||
tokenType | ||
), | ||
{ credentials: decoded } | ||
); | ||
} | ||
// valid key and credentials | ||
return h.authenticated({ | ||
credentials: | ||
credentials && typeof credentials === 'object' | ||
? credentials | ||
: decoded, | ||
artifacts: token, | ||
}); | ||
} catch (validate_err) { | ||
return h.unauthenticated(raiseError('boomify', validate_err), { | ||
credentials: decoded, | ||
}); | ||
} | ||
} | ||
// see: https://github.com/dwyl/hapi-auth-jwt2/issues/130 | ||
try { | ||
let { isValid, credentials } = await options.verify(decoded, request); | ||
if (!isValid) { | ||
return h.unauthenticated( | ||
raiseError('unauthorized', 'Invalid credentials', tokenType), | ||
{ credentials: decoded } | ||
); | ||
} | ||
return h.authenticated({ | ||
credentials: credentials, | ||
artifacts: token, | ||
credentials: { | ||
error: internals.FIRST_PASS_AUTHENTICATION_FAILED, | ||
}, | ||
}); | ||
} catch (verify_error) { | ||
return h.unauthenticated(raiseError('boomify', verify_error), { | ||
credentials: decoded, | ||
}); | ||
} | ||
const result = await internals.authenticate(token, options, request, h); | ||
if (result.error) { | ||
return h.unauthenticated(result.error, result.payload); | ||
} else if (result.response) { | ||
return h.response(result.response).takeover(); | ||
} else { | ||
return h.authenticated(result.payload); | ||
} | ||
}, | ||
@@ -264,3 +335,17 @@ /** | ||
*/ | ||
payload: function(request, h) { | ||
payload: async function(request, h) { | ||
if ( | ||
options.attemptToExtractTokenInPayload && | ||
request.auth.credentials.error === | ||
internals.FIRST_PASS_AUTHENTICATION_FAILED | ||
) { | ||
const token = extract(request, options); | ||
const result = await internals.authenticate(token, options, request, h); | ||
if (result && !result.error && result.payload) { | ||
request.auth.credentials = result.payload.credentials; | ||
request.auth.token = result.payload.token; | ||
} else { | ||
return result.error; | ||
} | ||
} | ||
const payloadFunc = options.payloadFunc; | ||
@@ -289,3 +374,3 @@ if (payloadFunc && typeof payloadFunc === 'function') { | ||
.then(() => h.continue) | ||
.catch(err => raiseError('boomify', err)); | ||
.catch(err => internals.raiseError(options, 'boomify', err)); | ||
} | ||
@@ -296,3 +381,3 @@ try { | ||
} catch (err) { | ||
throw raiseError('boomify', err); | ||
throw internals.raiseError(options, 'boomify', err); | ||
} | ||
@@ -299,0 +384,0 @@ } |
{ | ||
"name": "hapi-auth-jwt2", | ||
"version": "8.6.2", | ||
"version": "8.7.0", | ||
"description": "Hapi.js Authentication Plugin/Scheme using JSON Web Tokens (JWT)", | ||
@@ -43,2 +43,6 @@ "main": "lib/index.js", | ||
"user": "@salzhrani" | ||
}, | ||
{ | ||
"name": "Steven Leadbeater", | ||
"user": "@LedSysUK <stevenleadbeater@live.co.uk>" | ||
} | ||
@@ -62,3 +66,3 @@ ], | ||
"eslint-plugin-prettier": "^3.0.0", | ||
"nyc": "^13.1.0", | ||
"nyc": "^14.1.1", | ||
"pre-commit": "^1.2.2", | ||
@@ -65,0 +69,0 @@ "prettier": "^1.15.2", |
@@ -59,10 +59,10 @@ # Hapi Auth using JSON Web Tokens (JWT) | ||
// bring your own validation function | ||
const validate = async function (decoded, request) { | ||
const validate = async function (decoded, request, h) { | ||
// do your checks to see if the person is valid | ||
if (!people[decoded.id]) { | ||
return { isValid: false }; | ||
return { valid: false }; | ||
} | ||
else { | ||
return { isValid: true }; | ||
return { valid: true }; | ||
} | ||
@@ -74,8 +74,6 @@ }; | ||
// include our module here ↓↓ | ||
await server.register(require('hapi-auth-jwt2')); | ||
await server.register(require('../lib')); | ||
server.auth.strategy('jwt', 'jwt', | ||
{ key: 'NeverShareYourSecret', // Never Share your secret key | ||
validate: validate, // validate function defined above | ||
verifyOptions: { algorithms: [ 'HS256' ] } // pick a strong algorithm | ||
{ key: 'NeverShareYourSecret', // Never Share your secret key | ||
validate // validate function defined above | ||
}); | ||
@@ -88,4 +86,4 @@ | ||
method: "GET", path: "/", config: { auth: false }, | ||
handler: function(request, reply) { | ||
reply({text: 'Token not required'}); | ||
handler: function(request, h) { | ||
return {text: 'Token not required'}; | ||
} | ||
@@ -95,5 +93,6 @@ }, | ||
method: 'GET', path: '/restricted', config: { auth: 'jwt' }, | ||
handler: function(request, reply) { | ||
reply({text: 'You used a Token!'}) | ||
.header("Authorization", request.headers.authorization); | ||
handler: function(request, h) { | ||
const response = h.response({text: 'You used a Token!'}); | ||
response.header("Authorization", request.headers.authorization); | ||
return response; | ||
} | ||
@@ -104,11 +103,10 @@ } | ||
return server; | ||
}; | ||
} | ||
init().then(server => { | ||
console.log('Server running at:', server.info.uri); | ||
}) | ||
.catch(error => { | ||
console.log(error); | ||
.catch(err => { | ||
console.log(err); | ||
}); | ||
``` | ||
@@ -211,5 +209,9 @@ | ||
- `headerKey` - (***optional*** *defaults to* `'authorization'`) - The lowercase name of an HTTP header to read the token from. To disable reading the token from a header, set this to `false` or ''. | ||
- `payloadKey` - (***optional*** *defaults to* `'token'`) - The lowercase name of an HTTP POST body to read the token from. To disable reading the token from a payload, set this to `false` or ''. Please note, this will not prevent authentication falling through to the `payload` method unless `attemptToExtractTokenInPayload` is false | ||
- `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 `verify` callbacks (*not `validate`*) | ||
- `headless` - (***optional*** *defaults to none*) - set to an `object` containing the header part of the JWT token that should be added to a headless JWT token received. Token's with headers can still be used with this option activated. e.g `{ alg: 'HS256', typ: 'JWT' }` | ||
- `attemptToExtractTokenInPayload` - (***optional*** *defaults to* `false`) - set to `true` to let the `authenticate` method fall through to the `payload` method for token extraction | ||
- `customExtractionFunc` - (***optional***) function called to perform a custom extraction of the JWT where: | ||
- `request` - the request object. | ||
@@ -527,2 +529,59 @@ ### Useful Features | ||
## *How do I support users with JS disabled* | ||
An issue can arise when supporting users with JavaScript disabled when JWTs are too large to pass on query strings. | ||
With JS disabled, tokens cannot be added to headers by using redirects from OAuth providers in to the consuming service. | ||
Cloud providers will place limitations on URI lengths | ||
OAuth services may not always sit on a sibling subdomain of the protected service negating the use of a secure cookie | ||
The only way to pass a token in this case is to use either an HTML form with the token in a hidden field and a button with instructions for users to press the button if they have JS disabled and some JS that will submit the form automatically if it is enabled | ||
To configure `hapi-auth-jwt` to support this scenario, you will need to adapt the following sample configuration | ||
```js | ||
server.auth.strategy('jwt', 'jwt', { | ||
key: 'NeverShareYourSecret', | ||
// defining our own validate function lets us do something | ||
// useful/custom with the decodedToken before reply(ing) | ||
validate: (decoded, request) => true, | ||
verifyOptions: { algorithms: [ 'HS256' ] }, // only allow HS256 algorithm | ||
attemptToExtractTokenInPayload: true, | ||
// using yar as a session cache to store tokens, see: https://github.com/hapijs/yar | ||
customExtractionFunc: request => { | ||
if (request.auth && request.auth.token) { | ||
request.yar.set('token', request.auth.token) | ||
return request.auth.token; | ||
} | ||
const token = request.yar.get('token'); | ||
if (token) { | ||
return token; | ||
} | ||
} | ||
}); | ||
``` | ||
The configuration above will still run the normal token extraction attempts for headers, cookies, query string parameters and custom extraction. However, if there is no token successfully extracted, it will attempt to extract one from POST request bodies | ||
As the authentication phase of a HAPI request will apply scope protection before POST bodies are parsed, you will need to also define the route on which you will handle JWTs with no scope applied or the POST requests with JWT payloads will fail when you globally apply scope as part of your application | ||
```js | ||
server.route([ | ||
{ | ||
method: 'POST', | ||
path: '/', | ||
handler: (request, response) => response.redirect('/home'), | ||
config: { | ||
auth: { | ||
strategies: ['jwt'], | ||
payload: 'required' | ||
} | ||
} | ||
} | ||
]); | ||
```` | ||
This route will, when a JWT is posted failover from the authentication phase to the payload authentication phase, extract a JWT, store it in the YAR session cache and redirect the user to the `/home` path using a standard 302 response. When the handler for `/home` is JWT protected, the `customExtractionFunc` defined in the auth strategy will read the JWT from the users session cache and use it for authentication | ||
## *Advanced/Alternative* Usage => Bring Your Own `verify` | ||
@@ -529,0 +588,0 @@ |
@@ -11,53 +11,52 @@ const test = require('tape'); | ||
await server.register(require('../')); | ||
} catch(err) { | ||
} catch (err) { | ||
t.ifError(err, 'No error registering hapi-auth-jwt2 plugin'); | ||
} | ||
server.auth.strategy('jwt', 'jwt', { | ||
key: secret, | ||
validate: function (decoded, request, h) { | ||
if (decoded.id === 138) { | ||
throw new Error('ASPLODE'); | ||
} | ||
if (decoded.id === 139) { | ||
return { isValid: false } | ||
} | ||
if (decoded.id === 140) { | ||
return { isValid: false, errorMessage: 'Bad ID' } | ||
} | ||
return { response: h.redirect('https://dwyl.com') } | ||
}, | ||
verifyOptions: {algorithms: ['HS256']} | ||
}); | ||
server.auth.strategy('jwt', 'jwt', { | ||
key: secret, | ||
validate: function (decoded, request, h) { | ||
if (decoded.id === 138) { | ||
throw new Error('ASPLODE'); | ||
} | ||
if (decoded.id === 139) { | ||
return { isValid: false } | ||
} | ||
if (decoded.id === 140) { | ||
return { isValid: false, errorMessage: 'Bad ID' } | ||
} | ||
return { response: h.redirect('https://dwyl.com') } | ||
}, | ||
verifyOptions: {algorithms: ['HS256']} | ||
}); | ||
server.route({ | ||
method: 'POST', | ||
path: '/privado', | ||
handler: function (req, h) { return 'PRIVADO'; }, | ||
config: { auth: 'jwt' } | ||
}); | ||
server.route({ | ||
method: 'POST', | ||
path: '/privado', | ||
handler: function (req, h) { return 'PRIVADO'; }, | ||
config: { auth: 'jwt' } | ||
}); | ||
let options = { | ||
method: 'POST', | ||
url: '/privado', | ||
headers: {Authorization: JWT.sign({id: 138, name: 'Test'}, secret)} | ||
}; | ||
let options = { | ||
method: 'POST', | ||
url: '/privado', | ||
headers: {Authorization: JWT.sign({id: 138, name: 'Test'}, secret)} | ||
}; | ||
let response = await server.inject(options); | ||
t.equal(response.statusCode, 500, 'Server returned 500 for validate error'); | ||
options.headers.Authorization = JWT.sign({id: 200, name: 'Test'}, secret); | ||
response = await server.inject(options); | ||
t.equal(response.statusCode, 302, 'Server redirect status code'); | ||
t.equal(response.headers.location, 'https://dwyl.com', 'Server redirect header'); | ||
options.headers.Authorization = JWT.sign({id: 139, name: 'Test'}, secret); | ||
response = await server.inject(options); | ||
t.equal(response.statusCode, 401, 'Server errors when isValid false'); | ||
t.equal(response.result.message, 'Invalid credentials', 'Default error message when custom not provided'); | ||
let response = await server.inject(options); | ||
t.equal(response.statusCode, 500, 'Server returned 500 for validate error'); | ||
options.headers.Authorization = JWT.sign({id: 140, name: 'Test'}, secret); | ||
response = await server.inject(options); | ||
t.equal(response.result.message, 'Bad ID', 'Custom error message when provided'); | ||
t.end(); | ||
options.headers.Authorization = JWT.sign({ id: 200, name: 'Test' }, secret); | ||
response = await server.inject(options); | ||
t.equal(response.statusCode, 302, 'Server redirect status code'); | ||
t.equal(response.headers.location, 'https://dwyl.com', 'Server redirect header'); | ||
options.headers.Authorization = JWT.sign({id: 139, name: 'Test'}, secret); | ||
response = await server.inject(options); | ||
t.equal(response.statusCode, 401, 'Server errors when isValid false'); | ||
t.equal(response.result.message, 'Invalid credentials', 'Default error message when custom not provided'); | ||
options.headers.Authorization = JWT.sign({id: 140, name: 'Test'}, secret); | ||
response = await server.inject(options); | ||
t.equal(response.result.message, 'Bad ID', 'Custom error message when provided'); | ||
t.end(); | ||
}); |
163437
44
3266
746