Hello all, Happy New Year 2024 🎆
This is the continuation of my first blog. I strongly recommend reading this before you jump onto this.
https://blogs.sap.com/2023/12/16/session-handling-on-cpi-part-1/
As I mentioned at the end of my previous blog, we want to use two different users for services, but if we go with a message exchange/integration flow, I told the disadvantages.
Change the session handling to None😐. Then how?
So if the old cookie expires, a new session generated will overwrite the variable with new cookie details. Storing the cookie/session in a plain format over a variable is not a good practice.
S, I added one extra step with the help of hashing on nodejs and secure parameters on CPI.
I am generating hash on the node side with
The resultant hash will be stored on the session store against the session ID. So each time with a cookie they have to pass this secret. We will derive the hash again. If the derived hash and hash in the session match then only the session is valid, otherwise we can invalidate the session.
So if someone accesses the cookie, they are unable to use it until they get this secure parameter.
(i.e.: Hashing is also the costliest function in terms of execution, Maybe if there is a better secure way to store the cookie on the CPI side, please tell on comments ).
Two global variables for storing the respective cookie
user_secret – Passed on customer header and used on hashing at server side.
( Attaching All images in last section related to iflow)
const express = require('express')
var parser = require('body-parser')
var session = require('express-session')
const bcrypt = require("bcrypt")
const expressApp = express(); // Created Express App
//URL PARSING Middleware
expressApp.use(parser.urlencoded({
extended: true
}))
// New Mapping for storing Hash
let session_hash_map = new Map();
//JSON PARSING Middleware
expressApp.use(parser.json());
// SESSION Middleware
expressApp.use(session({
secret: 'dsufhodsfhdiajdbadkjbaoodhdjbfadljqslvsoif', // Usually a long secret maintained in environment. For testing I maintained here
cookie: {
httpOnly: true, // Prevent reading the cookie from other than HTTP
maxAge: 150000, // Validity of session on milliseconds
domain: 'localhost' // Domain for cookie
},
resave: true,
saveUninitialized: false,
name: 'Session_ID' // Name on session id cookie on response
}))
//Login Route
expressApp.get("/login", function (req, res, next) {
const message = req.session.id + req.headers.secretid // Session ID + secret coming from CPI
// console.log("Message to Hash:",message)
if (req.session.loggedin || req.session.loggedinadmin) { // IF already loggedin
console.log(req.session.id)
if (req.session.loggedin) {
console.log("Session Founded Normal User")
} else {
console.log("Session Founded Admin User")
}
// COmpares with original hash that stored during first time login
bcrypt.compare(message, session_hash_map.get(req.session.userid), function (err, result) {
console.log(result)
if (result) {
res.set('custom_status', 'A')
res.status(200).send({
"message": "Session Authorized succesfully"
})
}
//If hash not matches
else {
session_hash_map.delete(req.session.userid)
req.session.destroy();
res.status(401).send({
"message": "Session invalidated . Please Login Again"
})
}
});
} else {
const auth = new Buffer.from(req.headers.authorization.split(' ')[1], 'base64').toString().split(':'); // Getting user name and password from auth header
const user = auth[0];
const pass = auth[1];
if (user == "SAPCPI" && pass == '12345') { // For simplicity purpose
req.session.userid = user // user id property
req.session.loggedin = true; // Logged In Normal
bcrypt.genSalt(10, function (err, salt, res) {
// Hash and store it on map 🧑💻
console.log("New Login Normal")
bcrypt.hash(message, salt, function (err, hash) {
console.log("Derived hash Normal", hash) // Printing Hash
session_hash_map.set(user, hash) // Setting Hash
});
})
res.set('custom_status', 'N')
res.status(200).send({
"message": "Logged In succesfully and session set for 1 hour"
})
} else if (user == "SAPCPI_ADMIN" && pass == '98765') {
req.session.userid = user
req.session.loggedinadmin = true; // Logged in Admin
bcrypt.genSalt(10, function (err, salt, res) {
// Hash and store it on map 🧑💻
bcrypt.hash(message, salt, function (err, hash) {
console.log("New Login Admin")
console.log("Derived hash Admin", hash) // Printing Hash
session_hash_map.set(user, hash) // Setting Hash
});
})
res.set('custom_status', 'N')
res.status(200).send({
"message": "Logged In succesfully and session set for 1 hour"
})
} else {
res.status(401).send({
"message": "Invalid Credentials. Please try again"
})
}
}
});
// Employee Details
expressApp.get('/getemployeedetails', function (req, res) {
if (req.session.loggedin) {
console.log("Giving Employee Details")
res.status(200).send({
"meessage": "Here you can found employee details"
})
} else {
res.status(401).send({
"message": "Unauthorized. Please login"
})
}
})
// Customer Details
expressApp.get('/getcustomerdetails', function (req, res) {
if (req.session.loggedin) {
console.log("Giving Customer Details")
res.status(200).send({
"meessage": "Here you can found vendor details"
})
} else {
res.status(401).send({
"message": "Unauthorized. Please login"
})
}
})
// Payment details
expressApp.get('/getpaymentdetails', function (req, res) {
if (req.session.loggedinadmin) {
console.log("Giving Payment Details")
res.status(200).send({
"meessage": "Here you can found payment details"
})
} else {
if (req.session.loggedin) {
res.status(401).send({
"message": "You have no access to payment data.Contact Admin"
})
} else {
res.status(401).send({
"message": "Unauthorized. Please login"
})
}
}
})
expressApp.listen(8086, function () {
console.log("Server Listening on http://localhost:8086")
});
Iflow-1
Iflow-2
Setting secret in a custom header
Set Cookie For General User
Login Call-General User
Route
Write a Cookie in the variable
Set cookie in the header
Endpoint Call
Overwrite cookie
The process will be repeated for another set.
Cookie in Variable
1st call
Further, call until the session expires
output
In these two parts, I explained the session handling mechanism and how actually it works with the help of Node JS.
As I said this requirement is all self-created for this blog purpose, I hope it will give better/deep insight into session handling with nooks and corners which will be used in realtime scenarios.
If you have any queries/suggestions ( over CPI / Node JS ) feel free to comment.
Thanks…