Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cb is not a function in hapi-auth-jwt2 - Node.js

I'm following this tutorial to implement jwt authentication in hapijs v17.2.

I did everything according to the tutorial, but the following error is driving me crazy, even debugging didn't make any change.

error

Debug: internal, implementation, error
    TypeError: cb is not a function
    at Object.secretProvider [as key] (C:\Users\user\WebstormProjects\hapi-blog\node_modules\jwks-rsa\lib\integrations\hapi.js:30:14)
    at Object.authenticate (C:\Users\user\WebstormProjects\hapi-blog\node_modules\hapi-auth-jwt2\lib\index.js:123:87)
    at module.exports.internals.Manager.execute (C:\Users\user\WebstormProjects\hapi-blog\node_modules\hapi\lib\toolkit.js:35:106)
    at module.exports.internals.Auth._authenticate (C:\Users\user\WebstormProjects\hapi-blog\node_modules\hapi\lib\auth.js:242:58)
    at authenticate (C:\Users\user\WebstormProjects\hapi-blog\node_modules\hapi\lib\auth.js:217:21)
    at module.exports.internals.Request._lifecycle (C:\Users\user\WebstormProjects\hapi-blog\node_modules\hapi\lib\request.js:261:62)
    at <anonymous>

app.js

const hapi = require('hapi');
const mongoose = require('./db');
const hapi_auth_jwt = require('hapi-auth-jwt2');
const jwksa_rsa = require('jwks-rsa');
const dog_controller = require('./controllers/dog');

const server = new hapi.Server({
    host: 'localhost',
    port: 4200
});

const validate_user = (decoded, request, callback) => {
    console.log('Decoded', decoded);
    if (decoded && decoded.sub) {
        return callback(null, true, {});
    }

    return callback(null, true, {});
};

const register_routes = () => {
    server.route({
        method: 'GET',
        path: '/dogs',
        options: {
            handler: dog_controller.list,
            auth: false
        }
    });

    // Test
    server.route({
        method: 'POST',
        path: '/a',
        options: {
            handler: (req, h) => {
                return h.response({message: req.params.a});
            },
            auth: false
        }
    });

    server.route({
        method: 'GET',
        path: '/dogs/{id}',
        options: {
            handler: dog_controller.get
        }
    });

    server.route({
        method: 'POST',
        path: '/dogs',
        options: {
            handler: dog_controller.create
        }
    });

    server.route({
        method: 'PUT',
        path: '/dogs/{id}',
        handler: dog_controller.update
    });

    server.route({
        method: 'DELETE',
        path: '/dogs/{id}',
        handler: dog_controller.remove
    });
};

const init = async () => {
    await server.register(hapi_auth_jwt);

    server.auth.strategy('jwt', 'jwt', {
        key: jwksa_rsa.hapiJwt2Key({
            cache: true,
            rateLimit: true,
            jwksRequestsPerMinute: 5,
            // YOUR-AUTH0-DOMAIN name e.g https://prosper.auth0.com
            jwksUri: 'https://mrvar.auth0.com/.well-known/jwks.json'
        }),
        verifyOptions: {
            audience: 'https://mrvar.auth0.com/api/v2/',
            issuer: 'https://mrvar.auth0.com',
            algorithm: ['RS256']
        },
        validate: validate_user
    });

    server.auth.default('jwt');

    // Register routes
    register_routes();

    // Start server
    await server.start();

    return server;
};

init().then(server => {
    console.log('Server running at: ', server.info.uri);
}).catch(err => {
    console.log(err);
});

When I make a request to routes with auth: false, the handler works properly then I get the expected result, but requests to routes without auth return the following json :

{
    "statusCode": 500,
    "error": "Internal Server Error",
    "message": "An internal server error occurred"
}

More info:

node version: 8.9.4

npm version: 5.6.0

hapi version: 17.2.0

hapi-auth-jwt2: github:salzhrani/hapi-auth-jwt2#v-17

jwks-rsa: 1.2.1

mongoose: 5.0.6

nodemon: 1.15.0

like image 501
Hooman L Avatar asked Feb 19 '18 07:02

Hooman L


2 Answers

Both libraries has support for hapi v.17

I also faced this issue, but surprisingly both libraries has support for hapi v.17, but all documentation is based on old versions or didn't use this combination ;)

How to use it

There are few things that has to be changed then using hapijs v.17

Verify that you're using libraries versions which support hapijs 17:

  • "jwks-rsa": "^1.3.0",
  • "hapi-auth-jwt2": "^8.0.0",

Use hapiJwt2KeyAsync instead of hapiJwt2Key.

Information about this new async method is hidden in node-jwks-rsa package documentation

validate function has now new contract

Please change your existing validate function to below type:

async (decoded: any, request: hapi.Request): {isValid: boolean, credentials: {}} 

Working example (with handled passing scopes)

const validateUser = async (decoded, request) => {
    if (decoded && decoded.sub) {
        return decoded.scope
            ? {
                isValid: true,
                credentials: {
                    scope: decoded.scope.split(' ')
                }
            }
            : { isValid: true };
    }

    return { isValid: false };
};

server.auth.strategy('jwt', 'jwt', {
    complete: true,
    key: jwksRsa.hapiJwt2KeyAsync({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: env.auth.jwksUri
    }),
    verifyOptions: {
        audience: '/admin',
        issuer: env.auth.issuer,
        algorithms: ['RS256']
    },
    validate: validateUser
});
like image 143
michalczukm Avatar answered Sep 28 '22 09:09

michalczukm


The validate function changed in hapi@17 to not have a callback function. Based on your example it should now look something like this:

const validate = async (decoded, request) => {
  if (decoded && decoded.sub) {
     return { isValid: true };
  }
  return { isValid: false };
};

Part of the returned object can also include credentials which would represent the user that is authenticated and you can also do a scope as part of the credentials.

Then if you want to you can access the credentials as part of the request object like request.auth.credentials

like image 39
Shawn C. Avatar answered Sep 28 '22 08:09

Shawn C.