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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions