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