A clash of the old-school and new-school

Over the last couple of years at Shine we’ve built a number of Single Page Apps (SPAs) for large businesses. An app we recently built had to integrate with Oracle Access Manager (OAM), an Identity Management System (IDM) from everybody’s favourite enterprise software company. In this post I’m going to talk about a strategy that we used to successfully work around mismatches between the way that SPAs and OAM do things.

The Problem

We were building a SPA with Angular for our client. Our client mandated that OAM be used to secure our API endpoints. In short, this meant that OAM acted as a gateway to ensure that only logged-in users could do certain things. OAM also provided its own endpoints for logging-in and logging-out.

Using OAM was not negotiable – it was very much entrenched within the corporate ecosystem and required for single-sign to work across different parts of the business.

Our particular OAM installation was very old-school in that it assumed that our web app was always passing HTML from the server to the client. This meant that:

  • If you weren’t logged in and tried to go to a protected page, it would redirect you to a login page
  • When you logged-in successfully, it would redirect you to the logged-in home page
  • If your login attempt failed, it would redirect you to the login page with an error code encoded in query parameters
  • After you triggered a successful logout, it would redirect you back to the login page

Anybody who’s built a SPA would know that this is not the way SPAs normally work. By definition, SPAs only contain a single page, which usually makes XML HTTP Request (XHR) requests back to a server via a RESTful JSON API. Generally speaking, SPAs don’t make full-page browser requests, and they don’t expect HTML to be returned to them by the server.

Confronted by this incompatibility, it can be tempting to try to shoehorn a SPA into the full-page-reload model. This rapidly gets quite complex. For example, when logging-in you might try having the SPA navigate the whole browser to the login endpoint, then have OAM redirect back to the app URL, then have some server-side code examine the query parameters and inject them into the SPA when it boots.

As well as being both difficult to implement and test, this is also prone to some troublesome edge-cases – especially if the user uses the back button or bookmarks one of the URLs used en-route.

A Solution

Not many people realise that XML HTTP Requests (XHRs) – the HTTP requests that SPAs make from Javascript – follow redirects just like regular browser requests. This opened up an interesting option for us.

Like any IDM, the URLs that OAM redirects to are configurable. So we wondered: what if we configured OAM to redirect to an endpoint that returned some JSON rather than HTML? In a sense, this endpoint would act as a translator between OAM’s way of responding (i.e., redirecting to URLs) and our SPA’s preferred response type (HTTP status codes and JSON).

And where would this endpoint live? In our API, of course!

Some Examples

So what would this magical endpoint look like? I think the best way to understand it is to work through a couple of scenarios with accompanying sequence diagrams.

Access Denied

Firstly, let’s look at the case where the SPA makes a call to a protected /api endpoint and gets bounced by OAM because the user is not logged in. This might be because they have never logged-in before, or have logged-in previously but their session has timed-out.

Apropos of anything, let’s just say the app wants to get profile information for the user, so calls an /api/profile endpoint. With our special OAM callback in place, the sequence of calls might look something like this:


Our app submits an XHR to /api/profile. OAM intercepts it and, because the user is not logged-in, instructs the browser to redirect to our special API endpoint: /api/oamCallback. In the case of blocked access, OAM will also provide a request_id query parameter to this endpoint. We’ll need this later if the user does a login, and it’s also useful for letting our callback know what just happened.

The browser follows this redirect and does not yet return control to our SPA. Our implementation of the /api/oamCallback endpoint checks if the request_id parameter has been provided and, if so, returns a 401. Our SPA can intercept this 401 and switch itself to its login page.

It’s important to note that the SPA has no idea that a redirect took place – all it did was make a call to /api/profile and get back a 401, which it was able to process appropriately.

There’s one final important thing to note. You have to configure OAM to not block calls to the /api/oamCallback endpoint if the user is not logged-in. Otherwise, the redirect to the callback will _always_ be blocked in this scenario, meaning that OAM will try and redirect again, which will put the browser into an infinite redirect loop.

Unsuccessful Login

Now let’s consider a login attempt that fails because bad credentials are provided:


The first thing our app does is submit a login XHR to another special endpoint. This one is implemented by OAM and is called /oam/server/auth_cred_submit. Because OAM is old-school and is assuming this endpoint will be called from a HTML form, it expects the credentials to be form-encoded. However, it’s not hard for our SPA to craft an XHR that does this. Note also that OAM requires that we include the request_id in the form.

In the event of an unsuccessful login, OAM will invoke our callback with the request_id and another query parameter called p_error_code. Our callback detects that p_error_code was provided, and thus knows that an unsuccessful login attempt occurred. Consequently, it returns a 401 with a fragment of JSON that maps the error code to a message.

The SPA can detect this 401, extract the error message, and display it to the user.

Successful Login

OK, now it’s time for a successful login:


In this case, OAM redirects to our /api/oam-callback endpoint again, but this time without a request_id parameter or a p_error_code parameter. Our endpoint detects that neither of these parameters have been provided, so it knows that a successful login just occurred and thus returns a 200. Our SPA gets this 200, and is able to assume the login is successful.

A Quick Aside on Performance

At first all of these redirects look like a performance regression. Isn’t the app now making two network calls for each of these scenarios? Well, it’s true that two network requests are being made, but this isn’t a regression. Two requests would be being made even if weren’t using our fancy callback scheme.

Not convinced? Let’s revisit my initial summary of how our OAM installation was configured to behave under different usage scenarios:

  • If you weren’t logged in and tried to go to a protected page, it would redirect you to a login page
  • When you logged-in successfully, it would redirect you to the logged-in home page
  • If your login attempt failed, it would redirect you to the login page with an error code encoded in query parameters

Each of these scenarios still involve a redirect. Two calls are still being made to the server. It’s just that the whole browser window is following the redirect, rather than an XHR request that has been made by a SPA.


OK, now for our next scenario: logout. Unfortunately our callback scheme falls over for this one, but this is more because of OAM than anything else.

Like login, OAM has its own dedicated logout endpoint: /oam/server/logout. However, it turns out that some pretty crazy stuff happens when you call this endpoint: it returns a web page with some Javascript in it that executes when the page loads. This Javascript redirects the browser to another page. All of this needs to happen for OAM to continue to work correctly if you’ve used its single sign-on capabilities.

Unfortunately, for this Javascript to be executed when the page loads, you need to load the page in the browser, not via an XHR request. XHR requests won’t execute any Javascript that gets returned.

We experimented with embedding this call within a hidden iFrame, but weren’t able to get it going correctly. It also proved very difficult to debug. In the end we just had to revert to a full-page request to the logout endpoint, and have it redirect back to the app page. However, this didn’t turn out to be too much trouble because logout pretty much amounted to a full reset of the app anyway.


So there you have it – a (mostly) simple solution that bridges the gap between the worlds of SPAs and OAM. However, an astute reader may be asking themselves: how does the SPA know when it starts up whether the user has a session in progress or not?

There are a number of scenarios where the SPA can be considered to have to ‘start up’. The most obvious is when the user navigates to the SPA for the very first time and all of the assets for the SPA need to be loaded into the browser. Other scenarios are when the user navigates the browser away from the SPA to another page, then goes back to the SPA. Or it could be something as simple as the user forcing a refresh of the SPA page.

In each of these scenarios, the SPA is effectively starting from scratch. It won’t remember the state it was in the last time it was started, so it won’t remember whether the user was logged in or not. Even if it did (for example, by checking for the presence of a cookie or looking for something in local storage), we can’t really rely on the client to safely keep track of that sort of thing. Furthermore, something may have changed on the server-side that means the session is no longer valid.

In short, to figure out whether the user has an OAM session in progress, the SPA it has to make an API call. Which call? Well, any protected call will do, although it helps if it’s a call that we actually need anyway. A good candidate is something like the /api/profile endpoint that I used in my example scenarios. In the apps that I’ve built, we pretty much need profile data from the beginning anyway. For example, we often want to display the user’s name somewhere on the page to confirm to them that they’re logged-in.


Whilst the examples that I’ve presented in this post have been specific to OAM, the general strategy could probably be adapted quite well to any sort of web-service that insists on redirecting to URLs rather than returning status codes and/or JSON.

At first, it seems like a problem that can only be worked-around with nasty hacks. However, by introducing an intermediary service, you can bridge the gap in a reasonably elegant manner.


  1. I tried this approach but ran into a snag. When WebGate detects an unauthenticated request, it attempts to redirect to oamhost/oam/server/obrareq?blahblah, but the redirect fails because of the same-origin policy of the browser. We attempted to work around this by configuring a reverse proxy in the application’s web server to map apphost/oam/server/obrareq to oamhost/oam/server/obrareq but that failed when the OAM session was started by a 3rd party application (i.e. the session cookie was not accepted by the WebGate protecting my application). We tried to use CORS instead, but that failed because the origin header is set to “null” when OAM attempts to redirect back to the application. Did you not encounter similar issues?

    1. I am running into the exact same issue. I am wondering if there is a way to detect the CORS failure in the JQuery ajax() parameters’ error function, and do a full browser redirect to the obrareq URL.

    2. I’m afraid I’ve never seen that scenario – WebGate and OAM were on the same domain in my case, and we never had sessions being started by 3rd party applications (although we do share existing sessions with 3rd parties). Sorry I couldn’t be more help.

      1. Ben,

        Did you successfully configure the OAM+WebGate for this use case:
        1) If the user session is timed out, for a service api call, OAM will redirects it to api which will return 401 status code
        2) If the user is not logged-in, for a service api call, OAM will redirect it to api which will return 401 status code


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s