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.

Refresh is entirely client-side

This trips people up, because the endpoint looks like it caches. It does not.

pages/api/arcgis/token.ts calls ArcGIS on every request, asks for a 720-minute (12 h) token, and responds with Cache-Control: no-store. What is cached server-side is the client ID and secret — getArcGISCredentials fetches them once per server instance and holds them in memory, so Key Vault is hit once, not once per token.

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 endpoint may return a token that ArcGIS itself partially served from cache, the nominal 12 hours is an upper bound, not a promise — trusting it would let a token expire under a long-lived tab.

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.