Frame in Frame issue with SAML 2.0 timeout in ServiceNow

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.