Well Behaved AM Session with Quotas
Wouldn’t it be great to see a pop-up message telling you that your other existing sessions would be terminated if you proceeded to login sometimes (Figure 1)? If you have looked at ForgeRock AM 6.5 session management behaviours you have found that AM supports session quotas, and there are four different behaviours that can be applied in case users hit the limit. They are the following:
- Deny access
- Destroy next expiring
- Destroy oldest session
- Destroy all old sessions
AM applies the behaviour automatically and will display a message if access is denied due to the maximum number of session being reached.
So, what is the problem with that? Well here it is: we are not allowing users to choose a behaviour. So, if you leave your session open on your smart phone and you happen to login with another device you cannot control what happens to your previous session, and it may be that you terminate a precious session inadvertently. It might be a minor inconvenience if you were simply browsing a site casually, but if you were in the middle of a money transaction, or you had a basket full of your weekly shopping something dreadful can happen: before you realise, you have lost it all — and you have to do it all over again. From the user point of view this is a waste of time, and for the web site owner, this means potential loss of custom.
Before we look at a solution to this problem we must discuss something called Tree Based Authentication¹, and the Authentication Session concept. In a tree based authentication a dialogue goes back and forth between AM and the end user during which an authentication context is built up. For example AM might ask for a username, then it might silently request the device fingerprint, and some geo-location, then it might ask for a password. Every step is handled by a tree node, each time the context is added to the state of the Authentication Session. This state is called the shared state. This carries on until AM validates authentication challenges and reaches a decision to either return a success, or a failure. So what we are interested in doing is to intervene into this dialogue, preferably early, and do two things: check how many sessions we have already running for our users, then we can use a pop-up window to ask them what they want to do: login, or cancel. We will be using the shared state to capture the users’ responses. So what we need is an Authentication Tree improved with a few customised nodes of our own.
The project source code² can be found on GitHub. Our first node will be a Groovy script. In order to add such a script into AM 6.5.x you may follow the documentation³. Apply the instructions to create a new script and copy/paste the source code. Note you will have to add the imported Java classes to the white list of authorized Java objects⁴. The APIs used in the script require elevated credentials therefore the script obtains those through shared environment variables (alongside other parameterized values). Then the script uses those credentials to call the AM REST APIs and obtain the current number of active sessions for the user logging in.
The other important thing to notice is that the user password is saved into a variable and pushed into the aforementioned shared state. Going up two paragraphs we saw that the shared state is indeed what stores the dialogue context between AM and the user. In this case, pushing the password into this state is a necessity that requires extra considerations. First of which being that the password needs to be encrypted by the script using a standard AES algorithm. As discussed already, the shared state needs to reach the subsequent nodes therefore it has to be persisted throughout the login process. It can be configured to be stateful, i.e. to stay on the server side, either in memory, or through the Core Token Service⁵, but equally it can be left with its default settings traveling back and forth between AM and our users’ browsers, which is a stateless option. This is the reason the password is encrypted. Note, that the shared state is also typically encrypted (as per default), but it can be set to travel in clear text. Also, the communication for this would be happening over HTTPS so, the password ends up being encrypted thrice: twice in the payload, and once more in transit. Note that this can be handled differently from AM 7.0 which introduces a secure state to address this type of problem (with a built-in similar approach).
Groovy scripts are great for authentication trees because they allow to write custom nodes without the pesky code maintenance overhead of native nodes. However, Groovy scripts are server side, and we need to push some javascript to the browser. So we are going to use a little known convenient type of script called client side authentication. Here we can write a JavaScript pop-up which will deliver our session management message to end users. Again, the code is available on GitHub, and you need to follow the same copy/paste process as before.
The client side authentication script does not deliver itself to the browser though. We need a native node to do this job. Although such node is not provided by default in AM 6.5 one can be found in the MarketPlace hosted by ForgeRock. It is called the Client Script Auth Tree Node⁶. How to deploy a native custom node is not in scope here but that is no biggy because there is a dedicated user guide in the ForgeRock backstage documentation⁷. The native node will package a client side script (our JavaScript pop-up) and collect the result to be passed to the next node.
Our next node called CheckSessionResponse is receiving the response from the user. If the intention is to login the user password is decrypted and is shared for validation with the next node using the transient state. If the response is to cancel login then the authentication tree ends in failure (and existing sessions are not destroyed). Now I hear you say two things: why did I not mention that transient state earlier saving us all that password encryption debacle, and at which point in this is any session terminated? Unfortunately for us the transient state is not persisted if a node has a user callback (i.e. it interfaces directly with the user modifying the shared state) so we cannot use it because the pop-up node is effectively that callback. As for the session management we simply configure a behaviour such as Destroy All Sessions, or whichever is more relevant. If the login completes the behaviour applies and the new session is created. This way the script does not have to take care of session termination. Figure 2 shows our final Authentication Tree.
This shows how to improve on the default behaviours of AM 6.5 and provide users with the option to cancel a login in order to protect existing sessions. The pop-up only shows up when the user exceeds a maximum number of open sessions so the extra “login friction” inconvenience (i.e. the pop-up) is limited.
These instructions are meant for an AM 6.5 instance. This would in all likelyhood work on an AM 7.0 deployment however, if we are going to do that we would do well to simplify the Groovy scripts by removing the encryption functionalities and use the new built in ones.
[1] Find more information about Tree Based Authentication: https://backstage.forgerock.com/docs/am/7/auth-nodes/about-nodes.html#about-nodes
[2] https://github.com/JazzDeben/SessionManagement
[3] https://backstage.forgerock.com/docs/am/6.5/authentication-guide/index.html#manage-scripts-console
[4] https://backstage.forgerock.com/docs/am/6.5/dev-guide/index.html#script-engine-security
[5] https://backstage.forgerock.com/docs/am/6.5/install-guide/#chap-install-cts-implementation
[6] https://backstage.forgerock.com/marketplace/api/catalog/entries/AWAm-FCxfKvOhw29pnIp