Skip to content

refreshToken() should be a no-op when auth provider is Google IAP (AtlasGoogleSecurity) #3044

Description

@konstjar

Summary

In security.provider=AtlasGoogleSecurity (Google Cloud IAP) mode, Atlas force-logs the user out within minutes of every login. The browser console shows:

>>> Checking user token....
>>> Token close to expiring. Refreshing user token....
GET https://<host>/WebAPI/user/refresh 403 (Forbidden)   // or 404
Uncaught (in promise) {ok: false, status: 403|404, ...}

…followed by the UI rendering "Application initialization failed" with the Sign In button reappearing.

The underlying cause is that WebAPI/user/refresh is not implemented in AtlasGoogleSecurity — IAP mode never mints a WebAPI JWT, so there is nothing to refresh. Atlas, however, polls that endpoint anyway. Each failure triggers resetAuthParams(), which clears subject/permissions and signs the user out.

This ticket is for the Atlas-side fix.

Why Atlas calls a refresh it doesn't need

Two pieces of existing Atlas behavior, taken together, mean the call is both unnecessary and guaranteed to fire:

  1. Atlas already has IAP-aware session keep-alive. In js/services/AuthAPI.js, when loadUserInfo() sees the response header x-auth-provider: AtlasGoogleSecurity, an authProvider.subscribe(...) handler injects a hidden iframe pointing at /_gcp_iap/session_refresher. That iframe keeps Google's IAP session alive without any help from WebAPI.

  2. The token-expiry check fires constantly in IAP mode. WebAPI never sets localStorage.bearerToken in IAP mode, so authApi.token() is null and tokenExpirationDate() returns null. The check in js/Application.js then evaluates to:

   timeToExpire() = authApi.tokenExpirationDate() - new Date()
                   0 - currentEpochMillis
                   -1.78e12

…which is always less than refreshTokenThreshold (default 4h = 1.44e7), so:

   if (authApi.isAuthenticated() && this.timeToExpire() < config.refreshTokenThreshold) {
       authApi.refreshToken();
   }

…always fires. isAuthenticated() is true because loadUserInfo() set subject(info.login).

So Atlas calls WebAPI/user/refresh on essentially every interaction tick, the call fails (because IAP mode doesn't implement it), and Atlas signs the user out.

Proposed change

Extend the existing early-return in refreshToken in js/services/AuthAPI.js to also cover IAP:

 var refreshToken = function() {

-    if (!config.userAuthenticationEnabled) {
-        return Promise.resolve(true); // no-op if userAuthenticationEnabled == false
+    if (!config.userAuthenticationEnabled || authProvider() === AUTH_PROVIDERS.IAP) {
+        // No-op when auth is disabled, or when WebAPI is fronted by Google IAP.
+        // The IAP session is kept alive by the hidden /_gcp_iap/session_refresher
+        // iframe (see authProvider.subscribe above); WebAPI's AtlasGoogleSecurity
+        // does not expose /user/refresh.
+        return Promise.resolve(true);
     }

     if (!isPromisePending(refreshTokenPromise)) {
       refreshTokenPromise = httpService.doGet(getServiceUrl() + "user/refresh");
       refreshTokenPromise.then(({ data, headers }) => {
         setAuthParams(headers.get(TOKEN_HEADER), data.permissions);
       });
       refreshTokenPromise.catch(() => {
         resetAuthParams();
       });
     }

     return refreshTokenPromise;
 }

Both symbols already exist at module scope in this file:

  • AUTH_PROVIDERS is defined near the top: const AUTH_PROVIDERS = { IAP: 'AtlasGoogleSecurity' };
  • authProvider is the ko.observable populated from the x-auth-provider response header in loadUserInfo().

No other files need to change.

Affected versions

Reproduced on v2.15.x. Likely affects every version that contains AUTH_PROVIDERS.IAP and the authProvider observable (i.e. since IAP support was first added).

Reproduction

  1. Run Atlas against a WebAPI instance configured with security.provider=AtlasGoogleSecurity and Google IAP in front of WebAPI.
  2. Sign in via IAP; /user/me succeeds and the home page loads.
  3. Move the mouse around / interact with the page until the periodic check at js/Application.js:154 fires (≥ 30 user-interaction events).
  4. Observe GET WebAPI/user/refresh returning 403 or 404 and the UI reverting to the Sign In state with "no sources defined."
  5. Apply the patch above; verify the call is no longer made and the session remains stable indefinitely.

Tests

If a JS test for AuthAPI exists, add a case asserting that refreshToken() resolves immediately and makes no HTTP call when authProvider() is AUTH_PROVIDERS.IAP. Otherwise, manual verification per the reproduction steps above.

Notes

  • The matching WebAPI gap (no filter chain or filters wired for /user/refresh in AtlasGoogleSecurity) should be tracked and fixed separately so that future changes don't reintroduce a dependency on this endpoint from IAP-mode Atlas.
  • Workarounds people may already be using and that this PR makes unnecessary: seeding a user:refresh:get permission in the WebAPI security schema, or setting ATLAS_REFRESH_TOKEN_THRESHOLD to a large negative number to suppress the call.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions