Unable to integrate JWT authentication into `hapi.js`

Looking to implement the JWT strategy into my Hapi.js backend for heightened security and improved authentication processes. Here's a snippet of 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 () => {
    // Server setup and configurations
    const server = Hapi.Server({
        port: 4545,
        host: 'localhost',
        "routes": {
          "cors": {
              "origin": ["*"],
              "headers": ["Accept", "Content-Type"],
              "additionalHeaders": ["X-Requested-With"]
          }
      }
    });

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

    // JWT plugin registration
    await server.register(Jwt)

    // JWT authentication strategy
    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);

        // Custom validation logic

        console.log('Validation: End');

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

    server.auth.default('my_jwt_strategy');

    // Routes for login, register, and home
    server.route({
        method: 'POST',
        path: '/login',
        handler: async (request, h) => {
            // Authentication logic
            const { username, password } = request.payload;
            if (username === 'exampleUser' && password === 'examplePassword') {
                const token = Jwt.token.generate({ user: username }, 'some_shared_secret');
                return { token };
            } else {
                return h.response({ message: 'Invalid credentials' }).code(401);
            }
        },
        options: {
          auth: false
        }
    });
    // Other route definitions...

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

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

init();

Started with a register request to obtain a JWT token:

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);
});

Used the obtained token to send a request to the / route for JWT functionality testing, but encountered a 401 error:

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);
});

Answer №1

The reason for the failure is due to the JWT you're creating not containing the necessary claims for verification. I recommend adjusting your authentication configuration as follows:

server.auth.strategy('my_jwt_strategy', 'jwt', {
    keys: 'some_shared_secret',
    verify: {
        aud: 'urn:audience:test',
        iss: 'urn:issuer:test',
        sub: false,
        nbf: false, // Omit if not needed
        exp: true, // Ensure token is not expired
        maxAgeSec: 0, // Redundant with `exp` claim
        timeSkewSec: 0 // Probably unnecessary clock skew
    },
    validate: (artifacts, request, h) => {
// etc...
Jwt.token.generate(
  { 
      aud: 'urn:audience:test',
      iss: 'urn:issuer:test',
      /**
      * Consider using the username as the `sub` claim if it uniquely identifies the user. **/
      user: username
  },
  'some_shared_secret',
  {
      ttlSec: 14400 // Setting the `exp` claim to expire in 4 hours
  }
);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for sending information to a JavaScript variable through AJAX requests

Hello there, I'm currently working on a project that involves posting data stored in a JavaScript variable using AJAX. Can anyone assist me with the correct syntax for this process? <div class="container-fluid"> <div class="card shadow m ...

Transform HTML components into visual representations (on the server)

I am looking for a way to convert dynamic HTML elements into image files such as jpg or png. I do not want to capture a screenshot of a webpage or a static HTML file. My goal is to achieve something similar to the following, but executed on the server-sid ...

define` module root path

Currently, I am working with Typescript and my source files are located in the src directory. After transpiling, Typescript generates the output in the lib folder. This means that when I need to import components from my package, I have to specify the full ...

Issue reported: "Usage of variable 'someVar' before assignment" ; however, it is being properly assigned before usage

This piece of code showcases the issue: let someVar: number; const someFunc = async () => { someVar = 1; } await someFunc(); if (someVar == 1) { console.log('It is 1'); } As a result, you will encounter ...

Can a before hook ever run after a test in any situation, Mocha?

My before hook runs after the initial test and at the conclusion of the second test. Here is the code for my before hook: before(function () { insightFacade.addDataset("courses", content) .then(function (result: InsightResponse) { ...

What is the best way to differentiate the handling of a 401 Unauthorized response from other errors within an Angular 8 service that utilizes RxJS?

My REST API implementation requires an access token for user identification with each call. If the token is missing or expired, the endpoint returns a 401 UNAUTHORIZED response. There are instances where I make API calls using a timer in my service class: ...

``To increase a value within an array object in MongoDB with the use of nodejs, what steps should be

Within my node.js application, I have a MongoDB post model that includes an array of comments. Each comment has a "likes" field representing the number of likes it has received. I want to increment this value for each comment individually. How can I achiev ...

Displaying a pop-up message over the "Login button" for users who are not currently logged in

I am currently in the process of developing a website using node.js and express. I have successfully integrated all the login functionality through passport, allowing users to easily log in or out by utilizing res.user. However, I now want to enhance the ...

Is it possible to execute Ajax insertions in the right sequence without relying on async:false?

My ASP.Net MVC page contains an AJAX form that allows users to submit data manually or copy large amounts of data for submission at once using JavaScript. @using (Ajax.BeginForm("action", "controller", new AjaxOptions { HttpMethod = "POST", ...

Implementing lazy loading functionality with jQuery for an image grid within an AngularJS single page application

Recently, I decided to enhance my GUI for boom by incorporating Lazy Loading of images. This GUI is constructed using AngularJS. I have a working version that loads all images, which I have shared through this Gist link: https://gist.github.com/brock/67241 ...

Looking for the final entry in a table using AngularJS

Hey everyone, I'm dealing with a table row situation here <tbody> <tr *ngFor="let data of List | paginate : { itemsPerPage: 10, currentPage: p }; let i = index"> <td>{{ d ...

Using data-ng-repeat with various elements in Angular

Within the Angular controller, there is a variable that contains the following: $scope.items = [ { title: 'x', type: 'x'}, { title: 'y', type: 'y'} ]; Currently, there are only 2 items in the array, but there w ...

The Strapi registration feature in version 4 is giving a "method not allowed 405" error, which

Feeling a bit confused with a Strapi query. I am currently working on v4 and trying to set up a registration feature. The code snippet for invoking the function is provided below. Everything seems fine, such as submitting function variables, etc. However, ...

How can you use angularjs to dynamically adjust the style of an element based on the height of another element?

$('#reply').css("padding-top", $('.post:visible').height()-35 + "px" ); In this snippet, I am adjusting the CSS padding-top property of the element with the ID #reply to match the height of the visible .post element. While this approa ...

Adding elements to a list using the appendChild method

Hey there! I have a bunch of images and I'm trying to create a navigation list item for each image. Here's the code I currently have: var bigImages = $('#imagesList li img'); // grabs the Big Images Next, I've set up a ul with t ...

What causes the disparity between Chrome's print preview and printed output? [HTML - CSS]

In my Angular demo project, I have included basic text and a table. There is a print button that calls window.print() to print the page with applied styling. printPage() { window.print(); } CSS: @media print { @page { size: landscap ...

The functionality of the Ajax form is limited to a single use

Here's how my script is currently looking. Yes, it comes from a plugin. No, I'm not very familiar with ajax. Is there a way for me to make this form refresh and be ready to accept input again after the first submission? <script> jQuery(do ...

"Extracting information from a database in Angular Laravel to create a Chart.js display - a step-by-step

Currently, I am working on developing a dashboard application that includes various charts. My aim is to customize the data displayed in each user's chart based on information retrieved from a database. This is how my setup looks like: HTML <div ...

Issue encountered in Next.JS when attempting to validate for the presence of 'window == undefined': Hydration process failed due to inconsistencies between the initial UI and the server-rendered

I encountered an issue that says: Hydration failed because the initial UI does not match what was rendered on the server. My code involves getServerSideProps and includes a check within the page to determine if it is running in the browser (window==&apo ...

Unable to update a property with a new value in Vue.js when using the keyup.enter event on an input element bound to that property

I am facing an issue with inputs that have the @keyup.enter event assigned to a method that is supposed to reset the value of variables bound to these inputs to null. Here is an example of my code: methods:{ clear: function () { this.somethin ...