Ajax query failing due to OIDC SSO redirect

Viewed 34

I'm wondering what the standard solution is to ajax failing due to (OIDC) SSO redirect/refresh. It's insidious because the failure is silent, the web page appears to be working properly.

I have a simple web page with static HTML and simple javascript that populates the page via ajax and refreshes it periodically.

The webpage and JS are both access-controlled via the same OIDC SSO. This works, but can fail in the following ways when the ajax call is rejected 401 due to needing an authentication refresh. (This is not full password authentication, this is just "check that my token is ok, and see that it is, and keep going as if nothing had happened".)

Back end and front end are both served from the same server by a basic Apache service with the same Access Control and Authorization requirements.

  1. If a user navigates to the page in such a way that a cached version of the HTML is loaded and just the ajax runs. (e.g. back button)
  2. If the page is left sitting for long enough, I believe it refreshes will also fail for the same reason.

I have worked around the issue as shown below, but it feels like a hack, like there must be some much more standard way to do this.

// This function is called every 30s on a timer
function updateData(args, callback) {
    $.ajax({
        xhrFields: { withCredentials: true },
        url: "/getit?" + args,
        success: function(data) {
            localStorage.removeItem('pagereloaded')
            callback(data);
        },
        statusCode: {
            // Reload is because SSO tokens can timeout causing ajax to fail.
            // In these cases we want to reload the page right away. 
            // But what if it's just a genuine auth failure, we do not want to go into an infinite reload loop.
            // So pagereloaded and timer try to reload quickly a first time, but then avoid a loop after that.
            401: function () {               
                if (localStorage.getItem('pagereloaded') == null || (Date.now() - start_time) > 60000) {
                    localStorage.setItem("pagereloaded", 1)
                    location.reload();
                }
            }
        }
    });
}
1 Answers

WEB AND API MODULES

Sounds like the Apache module you are using is intended only for website requests, and is not intended for direct API requests. The more standard way to deal with this is via separate modules that are tailored to their clients - something like this:

PATH: https://example.com/www/** --> uses an OIDC module to verify credentials during web requests

PATH: https://example.com/api/** --> uses an OAuth module to verify credentials during API requests

If you search around you will see that there are a number of Apache modules available, some of which deal with security for web requests and some of which deal with security for API requests.

BEHAVIOUR

An API module should enable its clients to distinguish missing, invalid or expired API credential errors (usually classified as 401s) from other types of error. In normal usage, 401s should only occur in applications when access or refresh tokens expire, or, in some cases, when cookie encryption or token signing keys are renewed. These error cases are not permanent and re-authenticating the user will fix them.

Other types of error should return a different status code to the client, such as 400 or 500, and the client should display an error. As an example, if a client secret is misconfigured, it is a permanent error, and re-authenticating the user will not fix the problem. Instead it would result in a redirect loop. By testing these error conditions you will be satisfied that the behaviour is correct.

UPDATED CODE

You can then write simple code, perhaps as follows. The client side code should be in full control over behaviour after a 401. Whether you reload the page or just stop making background requests is up to you to decide.

function updateData(args, callback) {
    $.ajax({
        url: "/api/getit?" + args,
        success: function(data) {
            callback(data);
        },
        statusCode: {
            401: function () {               
                location.reload();
            }
        }
    });
}

Note also that the withCredentials flag is only needed for cross origin requests, such as those https://www.example.com to https://api.example.com, so I have omitted it from the above code, since it sounds like you have a same domain setup.

Related