javascriptjwthapi.jshapi

Cannot implement JWT authorization in `hapi.js`


I am interested in incorporating the JWT strategy into the backend of my Hapi.js application to enhance security measures and improve authentication processes. here is my server code :

const Hapi = require('@hapi/hapi');
const Joi = require('joi');
const Inert = require('inert');
const Vision = require('vision');
const HapiSwaggered = require('hapi-swaggered');
const HapiSwaggeredUI = require('hapi-swaggered-ui');
const knexConfig = require('./knexfile.js')
const knex = require('knex')(knexConfig.development)
const Jwt = require('@hapi/jwt')

const init = async () => {
    const server = Hapi.Server({
        port: 4545,
        host: 'localhost',
        "routes": {
          "cors": {
              "origin": ["*"],
              "headers": ["Accept", "Content-Type"],
              "additionalHeaders": ["X-Requested-With"]
          }
      }
    });



    server.ext('onPreResponse', (request, h) => {
      const response = request.response;
      if (response.isBoom) {
          // Error response, add CORS headers
          response.output.headers['Access-Control-Allow-Origin'] = 'https://hapi.dev';
          response.output.headers['Access-Control-Allow-Header'] = '*';

        } else {
          // Non-error response, add CORS headers
          response.headers['Access-Control-Allow-Origin'] = 'https://hapi.dev';
          response.headers['Access-Control-Allow-Headers'] = '*';

      }
      return h.continue;
    });

    await server.register(Jwt)



    server.auth.strategy('my_jwt_strategy', 'jwt', {
      keys: 'some_shared_secret',
      verify: {
          aud: 'urn:audience:test',
          iss: 'urn:issuer:test',
          sub: false,
          nbf: true,
          exp: true,
          maxAgeSec: 14400, // 4 hours
          timeSkewSec: 15
      },
      validate: (artifacts, request, h) => {
        console.log('Validation: Start');
        console.log('Decoded JWT:', artifacts.decoded);

        // Your validation logic here...

        console.log('Validation: End');

        return { isValid: true }; // Replace with your actual validation result
      }
    
    });




    server.auth.default('my_jwt_strategy');

    server.route({
        method: 'POST',
        path: '/login',
        handler: async (request, h) => {
            // Replace this logic with actual user authentication
            const { username, password } = request.payload;
            if (username === 'exampleUser' && password === 'examplePassword') {
                // Generate JWT token upon successful authentication
                const token = Jwt.token.generate({ user: username }, 'some_shared_secret');
                return { token };
            } else {
                return h.response({ message: 'Invalid credentials' }).code(401);
            }
        },
        options: {
          auth: false
        }
    });

    server.route({
        method: 'POST',
        path: '/register',
        handler: async (request, h) => {
            console.log('requested !')
            // Replace this logic with actual user registration
            const { username, password } = request.payload;
            // Implement user registration logic here, such as saving user to database
            
            // Generate JWT token upon successful registration
            const token = Jwt.token.generate({ user: username }, 'some_shared_secret');
            console.log(token)
            return { token };
        },
        options: {
          auth: false,
          cors: {
            origin: ['*'],
            headers: ['*'],
            credentials: true
          }
        }
    });

    server.route({
      method: 'GET',
      path: '/',
      handler: (request, h) => {
          const headers = request.headers;
          console.log('Request Headers:', headers);
          return 'Hello, World!';
      }
    });



    await server.register([require('./posts/posts_list'), 
                          require('./posts/posts_insert')]);
    await server.register([
        Inert,
        Vision,
        {
          plugin: HapiSwaggered,
          options: {
            info: {
              title: 'Test API Documentation',
              version: '1.0.0',
            },
          },
        },
        {
          plugin: HapiSwaggeredUI,
          options: {
            title: 'Swagger UI',
            path: '/docs', 
          },
        },
      ]);
       

    await server.start();
    console.log('Server started on port 4545 !');
};

process.on('unhandledRejection', (err) => {
    console.log(err);
    process.exit(1);
});

init();

First I make a register request and get a token like this:

await fetch('http://localhost:4545/register', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({username: 'balbalsbd', password: 'asdasdada'})
})
.then(response => {
    if (response.ok) {
        return response.json();
    } else {
        return Promise.reject({ status: response.status, message: response.statusText });
    }
})
.then(data => {
    console.log('Registration successful:', data);
})
.catch(error => {
    console.error('Registration failed:', error);
});

After that, with this token in hand, I proceeded to send a request via the console to the route / in order to examine the functionality of JWT, however, I was met with a 401 error code.

await fetch('http://localhost:4545/', {
    method: 'GET',
    headers: {
        'Authorization': `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYXNkc2QiLCJpYXQiOjE3MTExMTM5OTR9.5xND-Cp6G8w89R9Qpc6lWpVzf9CQ9lNM3mCk9EURYhw`
    }
})
.then(response => {
    if (response.ok) {
        return response.json();
    } else {
        return Promise.reject({ status: response.status, message: response.statusText });
    }
})
.then(data => {
    console.log('Response:', data);
})
.catch(error => {
    console.error('Error:', error);
});

Solution

  • Its failing because the JWT you are generating doesn't contain the claims you are verifying. I'd alter you auth config a bit:

    server.auth.strategy('my_jwt_strategy', 'jwt', {
        keys: 'some_shared_secret',
        verify: {
            aud: 'urn:audience:test',
            iss: 'urn:issuer:test',
            sub: false,
            nbf: false, // Not before: you probably don't need this in your usecase
            exp: true, // Verify the token isn't expired
            maxAgeSec: 0, // Made redundant by `exp` claim
            timeSkewSec: 0 // You probably don't need to skew your clock.
        },
        validate: (artifacts, request, h) => {
    // etc...
    
    Jwt.token.generate(
      { 
          aud: 'urn:audience:test',
          iss: 'urn:issuer:test',
          /**
          * Note: if the username represents a unique id
          * for the user, consider using it as the `sub` claim. **/
          user: username
      },
      'some_shared_secret',
      {
          ttlSec: 14400 // 4 hours, used to set the `exp` claim in the token.
      }
    );