Session Handling on CPI – Part 2
source link: https://blogs.sap.com/2024/01/07/session-handling-on-cpi-part-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Session Handling on CPI – Part 2
Introduction:
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/
Methodology used:
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?
- Log in with the first credential. We will get the response in the header Set-Cookie.
- Store the cookie in the global variable using the write variable option.
- Before calling the service, explicitly add the cookie header via the content modifier.
- Repeat the same strategy for another user.
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
- Message -> session ID + user_secret( secure parameter stored in CPI and transferred via custom header )
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 ).
Steps:
Two global variables for storing the respective cookie
- session_normal
- session_admin
user_secret – Passed on customer header and used on hashing at server side.
- Get the user_secret and transfer via custom header to the node.
- Check if general_user_cookie is available on the variable store. If available then set it onto the request header cookie and call the login endpoint.
- If the cookie is valid, the server performs the hashing and compares the hash. In case of the match it returns the status that the session was authorized else returns an error.
- If the cookie is expired/invalid/not passed, the server tries to authenticate and return the session in the Set-Cookie response header.
- If the 4th point has happened, then update the variable session_normal with a new one and modify the request header also.
- Call the employee endpoint.
- Repeat the same thing for the admin user with appropriate changes on settings.
( Attaching All images in last section related to iflow)
Node JS code:
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 Design :
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
Summary
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…
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK