Skip to main content

Earning the Map's Trust

Before the map draws a single tile, it has to earn AGOL's trust: the WebMap and its feature services are private, and every request the ArcGIS SDK makes needs a token. You already know the house rule — secrets never reach the browser — so the token gets minted by the BFF and handed to the SDK ready-made:

useArcGISAuth → GET /api/arcgis/token → ArcGIS OAuth (client_credentials)
│ │
│ └── secret from Key Vault (prod) or .env.local (dev)
└── IdentityManager.registerToken() for each whitelisted server

The one rule for consumers: any component that renders an <arcgis-map> or creates ArcGIS layers directly must call useArcGISAuth() and gate rendering on isReady. Render the map before a credential exists and the WebMap's own layer-metadata requests fire first, failing with ArcGIS error 499 "Token Required" — and those failures are not retried.

The hook​

hooks/useArcGISAuth.ts is the only thing that should touch IdentityManager. It fetches the token via useArcGISToken, then on every token change re-registers it for each URL in ARCGIS_AUTHENTICATED_SERVER_URLS, passing expires: tokenData.expiresAt so the SDK expires the credential in step with the real token. Re-registering overwrites the previous credential for the same prefix, which is what makes token refresh safe in a tab left open overnight.

It returns three things: isReady (true once a token has been registered at least once), isError, and tokenRef — a ref holding the current token string, needed for StreamLayer, whose wss:// connections bypass IdentityManager entirely and take the token as a custom parameter instead.

Registration is prefix-matched, so https://services8.arcgis.com/.../rest/services covers every FeatureServer beneath it. The list in data/constants.ts currently spans the ArcGIS Online portal root, the sharing/rest endpoint, the master WebMap item, the hosted feature-service root, and two us4-iot.arcgis.com entries for real-time streaming.

Minted once, refreshed client-side​

The token is a client-credentials grant, identical for every visitor, so the server mints it once and caches it for the process: lib/server/arcgisToken.ts asks ArcGIS for a 720-minute (12 h) token, re-mints ten minutes before expiry, shares one in-flight mint between concurrent callers, and backs off for 30 s after a failure. The root layout inlines the cached token into the HTML (bounded by an 800 ms wait) and RootLayoutClient seeds it into React Query, so the map mounts on first paint. pages/api/arcgis/token.ts answers from the same cache with Cache-Control: no-store and exists mainly for refreshes. The client ID and secret are cached separately by getArcGISCredentials, once per server instance.

All refresh logic lives in queries/useArcGISToken.ts. Both staleTime and refetchInterval are computed from the expiresIn the API actually returned, minus a 5-minute buffer, floored at 60 seconds. That indirection matters: because the cached token may already be hours old when a tab receives it, the nominal 12 hours is an upper bound, not a promise — trusting it would let a token expire under a long-lived tab. The full lifecycle, bootstrap included, is in the ArcGIS chapter's Token Handshake.

SettingValueWhy
staleTime / refetchInterval(expiresIn − 300s), min 60sRefresh before real expiry, not nominal expiry
refetchIntervalInBackgroundtrueA hidden tab left open for 12h still refreshes
refetchOnWindowFocus / OnReconnectfalseThe interval already covers it
retry / retryDelay3, exponential backoff capped at 30sTransient OAuth failures
gcTimestale time + 60sOutlive the refresh window

Credentials​

In production (NODE_ENV=production) the secret comes from Azure Key Vault via DefaultAzureCredential — managed identity in Azure, Azure CLI locally. Set AZURE_KEY_VAULT_NAME and store the pair as ESRIClientId and ESRISecret.

Everywhere else it reads .env.local:

ARCGIS_CLIENT_ID=...
ARCGIS_CLIENT_SECRET=...

Missing either throws ArcGIS client credentials not configured from the token route — a 500 at /api/arcgis/token, which surfaces as a map that never leaves its loader.

To mint a new pair: ArcGIS Online → Organization → Settings → Security → OAuth 2.0 → Add, and choose the Server application type (client-credentials grant requires it).

When the map won't load​

  • Stuck on the loader. isReady never flipped. Check the Network tab for /api/arcgis/token — a 500 means credentials; a pending request means Key Vault access.
  • Error 499 / "Token Required" on layer requests. Something rendered a map without gating on isReady, or the layer's server isn't in ARCGIS_AUTHENTICATED_SERVER_URLS.
  • One layer missing, everything else fine. Almost always the suppressed-challenge path above. Add the FeatureServer base URL — without the trailing /0, /1 layer index — to the whitelist.
  • Works for 12 hours, then breaks. Refresh isn't re-registering. Confirm the console shows a fresh Registered token for: line per server after each refetch.

Next: Tuning the Map, the chapter reference sheet — then Graphic Notifications.