Adding custom session data to Dreamfactory JWT token
When working with Dreamfactory it might be desired and helpful to have additional claims (key/values) in the user session JWT token.
In my scenario I have custom API services / scripts which do calls to other services (DB, SOAP, external REST API). However not all calls work with only the email from the user session but rather require certain IDs not related to Dreamfactory at all. So the services would first have to fetch the Id by email resulting in additional API calls all the time causing slower response times.
So I was looking for a way to store those key/values. Read on after the jump how I solved it.
Where could custom data be possibly stored ?
- Dreamfactory offers “custom settings” to be stored for each user. Those are available under DFs “user/custom” service. However, storing values which are required quite frequently, doesnt help much as its still another DB call to fetch them.
- In memory by using Redis. Actually I’m already doing this for caching other internal volatile data. See my other article on how to use DFs Redis cache. However this time I needed the data to be tied more closely to the user and not being volatile.
- Dreamfactorys own user session. DF uses JWT (JSON Web Tokens) for stateless sessions, allowing horizontal scalability. Hm.. sounds like a great place for custom session data. Tell me more about it !
To sum up the JWT concept:
Its a JSON object which holds typical session information like user_id, user_role, expiration time etc.. Those JSON objects are then base64-encoded and signed with a server-side secret. The JWT is given to the user on login and who has to pass it in for every API call which requires authentication. The server then verifies the signature and uses the session payload as usual.
From idea to implementation
So I dug into Dreamfactory df-core code to see which JWT lib it uses. Maybe I could reuse it to save some LOC and wouldnt have to decode/encode the JWT on my very own. (Which wouldnt be to hard tho).
After playing around for a little while (actually 3h reading code and docs about the JWT lib being used), it turned out to be quite simple:
Create a “Scripts” for the user.session.post.post_process event. Its fired when a user logs in, DF already handled all the authentication and response is just about to be returned to the user.
if(!array_key_exists('error', $event['response']['content'])) {
// get the session token and decode it to an array
$token = \DreamFactory\Core\Utility\Session::getSessionToken();
$payloadArray = \JWTAuth::manager()->getJWTProvider()->decode($token);
// add custom fields
$payloadArray['foo'] = "bar";
...
// re-encode the token which also re-signs it
$tokenNew = \JWTAuth::manager()->getJWTProvider()->encode($payloadArray);
// update DFs internal token-map used for expiring tokens when user changes password or gets deactivated
\DB::table('token_map')->where('token', $token)->update(['token' => $tokenNew]);
// update the response with our new JWT token
$event['response']['content']['session_token'] = $tokenNew;
$event['response']['content']['session_id'] = $tokenNew;
$event['response']['content_changed'] = true;
}
And thats it ! The resulting tokens are valid and have our custom session data and you can access them in any custom service or script like so
$payloadArray = \JWTAuth::manager()->getJWTProvider()->decode($token);
$foo = $payloadArray['foo'];
Note: I’m doing this with PHP as it has the advantage of using DFs own PHP classes. But you could aswell implement the same with NodeJS, Python..and then replacing the encode/decode calls with your own functions. Its only about base64 decoding/encoding and calculating and appending the HMAC signature based on DFs APP_KEY. The session token can also be retrieved with the global lookup {session.token}
Last but not least: Shouts to DFs Arif Islam who told me about the token_map and its importance !