Because ServiceNow was built on a framework of various Frames to built out its standard UI, there are some scenarios where a user will see framing issues in the application.
One common scenario where this happens is with SAML 2.0. If a user lets their session expire, and then then click on a link inside the browser, they will be directed to their IdP. After authentication is verified, the IdP redirects them back to ServiceNow. The problem here is that it does so in the right hand frame, rather than in the top window. This yields a result of a frame in frame rendering. If the user were to hit the refresh button, the app would be displayed properly. However, it is a bit ugly and most users wouldn’t think to hit the refresh button to fix the problem.
I often recommend to customers to modify their IdP login page to have frame busting javascript on the page so that it busts out of the ServiceNow frames. However, customers don’t always have that option, or they use Kerberos, which doesn’t display a form for authentication.
In order to get around that limitation, I have come up with a workaround that should work for most customers.
Create a UI Page that sets the SAML service URL as the top URL
I created a UI page that does a sys_property query and finds the SAML 2.0 Service URL that is used by the IdP to redirect us back to ServiceNow. The UI page then sets that URL as the top URL for the browser window (eliminating extra frames).
Here are the values I used for the UI page:
Name:
frame_bust
HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null"> <g:evaluate var="jvar_service_url"> var prop = new GlideRecord("sys_properties"); prop.addQuery("name", "glide.authenticate.sso.saml2.service_url"); prop.query(); prop.next(); prop.value; </g:evaluate> <script type="text/javascript"> if("${jvar_service_url}"){ top.location.replace("${jvar_service_url}"); } </script> </j:jelly> |
Modify the SAML Login Script
By default, if a user is not deep linking, meaning they are going to a Service-Now URL that is not a “nav_to.do” page, then we set the RelayState to navpage.do, which does not break us out of frames as it is a framed page. We need to modify the default of navpage.do to be our new frame_bust.do page.
To change this, we need to browse to the SAML 2.0 Login Script.
Once you are there, search for the following snippet of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 | if(!uriparam){ //No deep linking this.logDebug("No Deep Linking for this SAML request"); relayState = this.serviceURL; } else { //Deep Linking this.logDebug("There may be Deep Linking involved with this SAML request"); var destinationURI = ""; if(uriparam){ destinationURI += "?uri=" + encodeURIComponent(uriparam); } relayState = baseURL + "/nav_to.do" + destinationURI; } |
Just above that code, enter the following snippet:
1 2 3 4 | //Added to handle frame-in-frame situation if(!uriparam){ uriparam = "frame_bust.do"; } |
Hence, that section of code should now look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //Added to handle frame-in-frame situation if(!uriparam){ uriparam = "frame_bust.do"; } if(!uriparam){ //No deep linking this.logDebug("No Deep Linking for this SAML request"); relayState = this.serviceURL; } else { //Deep Linking this.logDebug("There may be Deep Linking involved with this SAML request"); var destinationURI = ""; if(uriparam){ destinationURI += "?uri=" + encodeURIComponent(uriparam); } relayState = baseURL + "/nav_to.do" + destinationURI; } |
The Result
Now when we reach ServiceNow without a valid session, and we are not deep linking, instead of sending a default relay state of: https://yourinstance.service-now.com/navpage.do, we send the relay state as https://yourinstance.service-now.com/frame_bust.do.
After the SAML authentication takes place, the user is redirected back to service-now with the frame_bust.do page still in the relay state. After Service-Now authenticates the user according to the SAML response, it will then redirect you to frame_bust.do. For a second or so while the page is rendering, you will see a frame-in-frame look, but as soon as the page renders, it will break out of the frame and redirect you to the home page (navpage.do).
PLEASE NOTE
With the introduction of the “SAML 2.0 Update 1” plugin, some of the locations of this code have been moved to Script Includes instead of Installation Exits. You may be interested in checking out a ServiceNow Community post on how to implement my frame-in-frame busting code with SAML 2.0 Update 1. Many thanks to Jason Petty for this update.
Fantastic. We have this frame in frame problem. I will definitely try this. Thanks very much.
John,
Quick question. Shouldn’t the code read with a backslash in front of the “frame_bust.do”?
Like so
uriparam = “/frame_bust.do”;
@Ryan, In some cases, you would want the leading ‘/’ character, however, it does not apply in this situation as the method I am using to grab the uri parameter returns the parameter without a ‘/’ character. Therefore, I set it without one as well.
John,
Do you have example instances where this has been successfully implemented? We are noticing that while the functionality ‘works’, it can have a side effect of spamming the logs with an inordinate amount of data (1 fairly large chunk every second). This brings operational challenges which will impact performance and potentially ongoing availability of folks who use it.
Best,
Alex North
Tech Support Manager
@Alex,
I know I have customers using this in the past, but no one has come back saying that the logs were getting large with this solution. What type of logging data is being entered because of this? Do you have debug logging enabled on the SAML 2.0 plugin? If so, do you see the large amount of logging with the debug logging option turned off?
I’m not sure I understand the ‘if’ logic in the code.
In particular, we have
if (!uriparam) then uriparam = framebust
then we have
if (!uriparam) …
which won’t ever be true since we just set it.
Is the idea to modify the original code as little as possible and that is why we are inserting it this way?
@lou – Yes, that was exactly the reason. If we do have to modify the existing SN code, I prefer to leave it as much intact as possible so that it is easy to manage or back out if necessary to get any future upgrades of the script.