For most of my career, authentication was the thing I avoided. Configure the app, set up the layers, build the popups — and when it came time to handle login, find a workaround or ask someone else.
OAuth was especially opaque. Flows, codes, tokens, redirects, client IDs, client secrets. A wall of terminology that seemed designed to discourage anyone without "backend developer" in their title.
Then I spent one afternoon understanding the mental model. Not implementing it. Understanding it. Everything since has been pattern repetition.
What OAuth actually is
OAuth answers one question: how does your app get permission to access data on behalf of a user, without ever seeing that user's password?
The answer: your app never touches the password. The identity provider does. Your app receives a token that proves the user authenticated successfully.
That's the whole thing. Everything else is mechanics.
The AGOL flow — five steps
Step 1: Your app sends the user to Esri's login page.
You construct a URL with your client ID and redirect URI. The user sees Esri's login page — not one you built. They enter their AGOL credentials (or SSO). You never see those credentials. They never touch your code.
Step 2: Esri redirects back with a code.
After successful login, Esri sends the user back to your redirect URI with a short-lived authorization code. This code is not a token. It's a one-time proof that the user authenticated. It expires in minutes.
Step 3: Your server exchanges the code for a token.
Your server — never the browser, this is the important part — sends the authorization code to Esri along with your client secret. Esri returns an access token.
Step 4: Your app uses the token.
Every AGOL API request includes this token. Private layers respond. Org content is accessible. The token represents the user's identity and permissions.
Step 5: Token expires.
AGOL tokens expire — 2 hours by default. Your app either silently refreshes via a refresh token, or prompts the user to log in again.
That's the whole flow. Step 3 is the only non-obvious one: the code exchange happens server-side, never in the browser. Once that clicks, everything else makes sense.
The two client IDs — and why mixing them up breaks everything
When you register an app in AGOL, the type of application determines what flows are available.
Browser / Web App client: supports redirect URIs like https://yourdomain.com/callback. Used for any web app where users log in via a browser. This is the right choice for any publicly deployed HTML app.
Server / Script client: supports Out-of-Band (OOB) flows. The redirect URI is urn:ietf:wg:oauth:2.0:oob — instead of redirecting a browser, Esri shows the user a code on screen to copy and paste.
Using a Server/Script client ID in a browser app: Invalid redirect_uri. Every time.
Using a Browser client ID in a Python script: Invalid redirect_uri. Every time.
Register the right type for the right use case. Label them clearly in AGOL — "Python OAuth Login" and "Web App Production" — because the client ID itself is a long alphanumeric string that tells you nothing.
The SDK shortcut
For ArcGIS JavaScript SDK apps, you don't implement the OAuth flow manually. OAuthInfo and IdentityManager handle everything:
const info = new OAuthInfo({
appId: "YOUR_CLIENT_ID",
popup: false,
});
esriId.registerOAuthInfos([info]);
esriId.checkSignInStatus(portal + "/sharing")
.then(() => loadApp())
.catch(() => esriId.getCredential(portal + "/sharing"));
When the user isn't authenticated, getCredential redirects them to the AGOL login page. When they return, the SDK picks up the token automatically. You never write a token exchange. You never manage redirects. This is the minimum viable implementation for any thin HTML app that needs authentication.
The OOB flow for scripts
For Python scripts or CLI tools, the Out-of-Band flow:
- Open the authorization URL in a browser — I run
open "URL"from the terminal, it launches automatically - User logs in, clicks Allow
- AGOL shows a code on screen
- User copies the code, pastes it into the terminal
- The script exchanges the code for a token via REST
import requests
code = input("Paste authorization code: ")
response = requests.post(
"https://www.arcgis.com/sharing/rest/oauth2/token",
data={
"client_id": "YOUR_SCRIPT_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"grant_type": "authorization_code",
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
"code": code,
}
)
token = response.json()["access_token"]
From there, every AGOL REST request uses ?token={token}. No credentials in the script. The token expires, so there's nothing useful to steal from the code itself.
Why not username/password
Red Cross org has mandatory two-factor authentication. Username/password auth at the API level doesn't support 2FA. It will fail.
Beyond that: it's a legacy pattern Esri is actively deprecating. It requires storing a password somewhere — in code, in a config file, in a .env. Any of those is a credential leak waiting to happen.
OAuth tokens expire. Passwords don't. An expired token is useless. A leaked password is usable indefinitely until someone notices.
OAuth is the right pattern. For any modern AGOL org, it's also the only pattern that works.
Tips that would have saved me time
Register your redirect URI exactly. Trailing slash matters. https://yourapp.vercel.app and https://yourapp.vercel.app/ are different URIs. AGOL will reject the one that doesn't match.
Register multiple URIs in one app. Both localhost and your production URL. One client ID works for local development and production.
The expiration parameter controls token lifetime. &expiration=20160 in the authorization URL gives you a 2-week token. For scripts you run repeatedly, this eliminates constant re-authentication.
Tokens aren't secrets after use. An expired AGOL token is worthless. Focus credential hygiene on client secrets and passwords — not on expired tokens.
The SDK persists tokens in browser storage. Users who "Remember Me" on AGOL won't be prompted on the next visit. This is expected — document it so users don't think the app is broken when it skips the login screen.
OAuth felt like a wall until I understood it was five steps with one non-obvious rule. The rule is: the code exchange happens server-side. Once that clicked, everything else was pattern recognition.
Related: Security First — Building Tools That Assume Breach — The Thin HTML Deploy Pattern