Sunday, October 28, 2018

APEX Multi-tab session collision issue


Ever since I have started developing APEX applications (HTML DB back then), I haven’t stopped hearing complaints from users about their APEX applications wrongly updating session state when they open their application in multiple browser windows or tabs. Basically, APEX applications (as well as many other non-APEX web applications) have a problem when same application’s session is opened in multiple browser windows or tabs, causing session state updated in one window/tab to collide with session state in another window/tab.
Unfortunately, the only resolution I could offer was to request users to either not open their APEX application in multiple windows/tabs (which was a show stopper in some cases), or to use different browser “types” to open their application in multiple windows since APEX Engine treats each browser type as a different client (Ex. if the application is running in IE, then users can copy/paste the desired page URL into a Chrome window, which will force APEX engine to generate and assign a new APEX session for the application in Chrome). The downside of the latter workaround was having to deal with browser compatibility issues, and that some users had only one type of browsers on their computers due to security constraints by I.T.

APEX 5.1 introduced a new request to the APEX engine named APEX_CLONE_SESSION. When this request is added to an APEX URL from an existing APEX session, it will automatically generate a new APEX session ID and associate it to the current browser’s session cookie along with original session state. Joel Kallman has nicely documented this new request, so you can learn more about how it works and how it can be enabled in your workspace here.

So, APEX_CLONE_SESSION is a great feature that I think APEX community has been waiting for so long, but does it completely address the problem from user’s perspective? In order to answer that question, let’s take a peek at how it is prescribed to be used. Technically, this new APEX request seems to be designed to mainly provide and alternative method for opening APEX screens in different windows/tabs through adding a link or button in your application (Ex. “Open in new window/tab” button) that users should click on to open current APEX screen in another window/tab. Developers need to write some code to create that link/button so it constructs a target APEX URL based on current APEX page URL along with APEX_CLONE_SESSION request inserted in the request portion of the URL, then redirect (in a new tab or window) to the newly constructed URL.

Apparently, neither coding nor placing the button/link where it can be available to all application’s screens is a big deal. However, this won’t prevent users from continuing to open APEX screens in other windows/tabs manually (either through right clicking on links + open in new window/tab, or through copying current screen’s URL and pasting it into another window/tab). So this fix still relies on users’ commitment not to manually open their APEX application screens in multiple windows/tabs.

Ideally, you want to enhance this fix so that it either prevents users from manually opening their APEX application in other windows/tabs, or allow users to continue to open windows/tabs manually but seamlessly isolate APEX sessions in different windows/tabs. I personally always prefer solutions that come with less restrictions to users even that they usually take more time and efforts to implement, so I decided to develop the second solution that allows for multiple windows/tabs.

So how should it really work?

The key idea is that you want to somehow associate each active APEX session ID with its current window/tab and be able to validate that association upon rendering each page of your applications (you may need to make some exclusions, but let’s not worry about this for now). Then, whenever your APEX application is rendered in a browser/tab, you want to add some sort of validation (before rendering the page) that checks if the corresponding APEX session ID is already associated with some other window/tab (i.e. open in another window/tab).
If not, then the page will be rendered normally, after associating the current browser/tab with the current APEX session ID so other windows/tabs know that this APEX session ID is already used and associated with this window/tab. If APEX session ID is already associated with another window/tab, then it won’t complain about it nor will it throw an error informing users that they can’t open same APEX session in multiple windows/tabs. Instead, it will peacefully render that APEX page but seamlessly assign a new APEX session to it by programmatically utilizing APEX_CLONE_SESSION request. Makes sense? If not, then let’s see if a logic flowchart helps demonstrating the idea.

Logic Flowchart



1) Upon loading your APEX application’s pages in a browser’s window/tab, execute JavaScript shown code below.

2) The JavaScript code checks if the value of window.name property is equal to some value of your choice (ex. “SafeAPEXWindow).
Window.name is a browser property that is nullified by default, but you can populate it with a value of your choice that you can then reference through the lifetime of that window/tab.

     2.1) If window.name is not equal to “SafeAPEXWindow”, then do the following:

           2.1.1) Check if this is a brand new window/tab. In this case, window.name will be either null, or has some value that does not use our win_<timestamp> format. Both imply that this is the first time this window/tab is being used to render your APEX application, which is all we need to know at this point.

                 2.1.1.a) If brand new window/tab, then assign a unique value to window.name (Ex. win_<timestamp>).

           2.1.2) Check if P0_WINDOWNAME is null. P0_WINDOWNAME is an APEX hidden item you need to create on page 0.

                       2.1.2.b) If null, this indicates that this APEX session has not been associated with a window/tab yet. In this case, assign window.name value to P0_WINDOWNAME. This establishes window/tab to APEX session ID association.

           2.1.3) Check if value of window.name = value of P0_WINDOWNAME:

                       2.1.3.1) If yes, this implies that this APEX session is already associated with this window/tab, so stop here and continue to render the page normally.

                       2.1.3.2) If no, this implies that this APEX session is already associated with some other window/tab, so it should seamlessly render this page in this browser but using a new APEX session ID (without losing session state), which can only be accomplished by the utilizing APEX_CLONE_SESSION as follows:

                                   2.1.3.2.a) Assign some custom value like “SafeAPEXWindow” to window.name.

                                   2.1.3.2.b) Construct target URL based on current page’s URL, along with APEX_CLONE_SESSION request inserted into that URL.

                                   2.1.3.2.c) Redirect to the newly generated URL. This goes to step 2.2 below which ends by rending your APEX page with a newly generated APEX session ID, so it won’t collide with APEX sessions opened in other windows/tabs.

     2.2) If window.name = SafeAPEXWindow, then this is coming from 2.1.3.2 redirect which implies that this page’s URL is a newly generated URL through APEX_CLONE_SESSION so it is safe to render it as is, after establishing window/tab to APEX session ID association through the following steps:

                       2.2.1) Assign a unique value to window.name (Ex. win_<timestamp>). This is basically a way to assign unique ID to each tab or window you are browsing your application with.

                       2.2.2) Assign same window.name value to P0_WINDOWNAME item. This associates the new AEPX session ID with the window/tab.

                       2.2.3) Continue to render the page normally.


JavaScript Code





Required APEX changes
You need to make the following simple changes in your APEX application in order to utilize this fix across the application:

1) In Page 0, create a hidden item P0_WINDOWNAME

2) In page 0, create a static region with following properties:
Name: something like “ValidateMultiWindow”.
Sequence: 0 (you want to make sure this is the very first region to be rendered).
Source >> Text: copy & paste the JavaScript above.
Server-side Condition: You can write some code here to exclude certain pages. For examples, pop-up pages do not require this fix as their address bar is usually disabled so user won’t be able to copy/paste their URL. You do not want to use APEX_CLONE_SESSION if not needed to avoid creating unnecessary APEX session IDs.
You can either exclude by filtering out certain page IDs for these pop-up page if you can easily identify them, or write some query like the following (using “no rows returned” condition type):


select 1
from APEX_APPLICATION_PAGES
where 
application_id = :APP_ID -- current APEX app
and page_id = :APP_PAGE_ID -- current APEX page
and (
lower(page_template) like '%pop%' --pages using pop-up template
or lower(page_template) like '%login%' --pages using login template (usually login page only)
)

3) Make sure the APEX_CLONE_SESSION request is enabled in your APEX workspace. Refer to Joel Kallman’s post referenced above for more details.

4) Create Application Process “SET_WINDOW_NAME” with process point = Ajax Callback, and in PL/SQL Code just type in: null;
Set Authorization Scheme to “No Authorization Required”.

And you’re all set!!

Note 1: Cloned session(s) will continue to use the same browser cookies created by original session. Therefore, if any of these sessions log out or expire, all other session(s) will automatically log out/expire as well.

Note 2: This fix also helps making sure that APEX_CLONE_SESSION request is not abused. It clones a session only if necessary, and does not simply add APEX_CLONE_SESSION to each and every navigation/branch in the application. I have actually seen developers adding APEX_CLONE_SESSION request to each navigation/branch in their applications, which results in hundreds (if not thousands) of unnecessary APEX sessions that might cause performance issues.

Note 3: This fix requires APEX 5.1 or later.

2 comments:

  1. Great post - we have used similar techniques such as generating unique page ids on the loads but this allows it to be at application level over page level.

    I have also tweaked your JS because the redirect function will not work if the user copies a URL without colons after the session id due to the third and fourth colon not existing.

    ReplyDelete
  2. Does APEX page loaded twice when it is opened in new tab for first time?

    ReplyDelete