ArcGIS Authentication Skill
Purpose
Establish secure, consistent authentication patterns for all ArcGIS Online/Enterprise API interactions — both in Python scripts AND in Claude chat sessions where Claude needs to query org-private ArcGIS services directly.
Authentication Decision Tree
User shares an ArcGIS REST service URL
→ Can Claude fetch it without a token?
→ YES → Query it directly, no auth needed
→ NO (403 / "Token Required") →
Does Claude have a saved token in this session?
→ YES → Use it
→ NO →
Ask: "Do you have a Client ID and Client Secret from a registered ArcGIS app?"
→ YES → Proceed to 3-Legged OAuth (Section A)
→ NO →
Ask: "Can you generate a temporary token?"
Options:
a) Generate from AGOL: sharing/rest/generateToken (Section B)
b) Copy from registered app's settings page (Section C)
c) Export data as CSV and upload (fallback)
IMPORTANT: Why 2-Legged Tokens Fail for Org Content
Client credentials (2-legged) tokens represent the APP, not the USER. They CANNOT access org-private content. This has been confirmed repeatedly. NEVER attempt to access org-private services with a client_credentials token — it will always return 403.
Section A: 3-Legged OAuth in Claude Chat (PROVEN WORKFLOW)
This is the primary method when Claude needs to access org-private ArcGIS services during a chat session. It uses the OAuth "out-of-band" (OOB) redirect so the user can copy-paste an auth code without needing a running web server.
Prerequisites
- User has a registered ArcGIS application with Client ID and Client Secret
- User can log into ArcGIS Online in their browser
Step 1: Get Client Credentials from User
Ask user for their Client ID and Client Secret from a registered ArcGIS app.
Step 2: Generate Authorization URL
import urllib.parse
client_id = "USER_CLIENT_ID"
auth_url = "https://www.arcgis.com/sharing/rest/oauth2/authorize?" + urllib.parse.urlencode({
'client_id': client_id,
'response_type': 'code',
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'expiration': 120 # minutes
})
print(auth_url)
Present this URL to the user with instructions:
- Click/open the URL in your browser
- Log in with your ArcGIS credentials
- Authorize the app
- ArcGIS will display an authorization CODE on screen
- Copy that code and paste it back here
Step 3: Exchange Code for User Token
import urllib.request
import urllib.parse
import json
client_id = "USER_CLIENT_ID"
client_secret = "USER_CLIENT_SECRET"
auth_code = "CODE_FROM_USER"
token_url = "https://www.arcgis.com/sharing/rest/oauth2/token"
token_data = urllib.parse.urlencode({
'client_id': client_id,
'client_secret': client_secret,
'grant_type': 'authorization_code',
'code': auth_code,
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob'
}).encode()
req = urllib.request.Request(token_url, data=token_data)
with urllib.request.urlopen(req, timeout=30) as resp:
token_resp = json.loads(resp.read().decode())
token = token_resp.get('access_token')
refresh_token = token_resp.get('refresh_token', None)
expires_in = token_resp.get('expires_in', 0)
username = token_resp.get('username', 'unknown')
Step 4: Verify Identity
self_url = f"https://www.arcgis.com/sharing/rest/community/self?f=json&token={token}"
with urllib.request.urlopen(self_url, timeout=30) as resp:
me = json.loads(resp.read().decode())
print(f"Authenticated as: {me.get('username')} ({me.get('fullName')})")
print(f"Role: {me.get('role')}")
print(f"Org: {me.get('orgId')}")
Step 5: Save Token for Session
with open('/home/claude/agol_token.txt', 'w') as f:
f.write(token)
Step 6: Query Protected Services
Append &token={token} to any ArcGIS REST API call:
svc_url = f"https://services.arcgis.com/ORG_ID/arcgis/rest/services/SERVICE_NAME/FeatureServer?f=json&token={token}"
Token Lifetime
- Access token: ~30 minutes (varies by org settings)
- Refresh token: ~2 weeks
- If token expires mid-session, use refresh token to get a new one without re-prompting user
Refresh Token Usage
refresh_data = urllib.parse.urlencode({
'client_id': client_id,
'grant_type': 'refresh_token',
'refresh_token': refresh_token
}).encode()
req = urllib.request.Request(token_url, data=refresh_data)
with urllib.request.urlopen(req, timeout=30) as resp:
new_token_resp = json.loads(resp.read().decode())
new_token = new_token_resp.get('access_token')
Section B: Direct Token from generateToken Endpoint
User generates a token manually at the ArcGIS REST endpoint.
Instructions for User
- Go to:
https://www.arcgis.com/sharing/rest/generateToken - Enter AGOL username and password
- Set "Webapp URL" to
https://www.arcgis.com - Set expiration (max ~14 days / 21600 minutes)
- Click Generate
- Paste the token here
Usage
TOKEN = "USER_PROVIDED_TOKEN"
url = f"https://services.arcgis.com/ORG_ID/arcgis/rest/services/SERVICE/FeatureServer?f=json&token={TOKEN}"
Section C: Temporary Token from Registered App Page
The registered app settings page in AGOL shows a "Temporary Token" that lasts 1-2 hours. This is actually a CLIENT CREDENTIALS token (2-legged) and WILL NOT work for org-private content. Only use this for public services.
Section D: Python Script Authentication (for .py files, notebooks)
For scripts running locally or on a server — NOT in Claude chat.
Environment Variables (.env)
ARCGIS_ORG_URL=https://arc-nhq-gis.maps.arcgis.com
ARCGIS_USERNAME=your_username
ARCGIS_PASSWORD=your_password
# OR for OAuth:
ARCGIS_CLIENT_ID=your_app_client_id
ARCGIS_CLIENT_SECRET=your_app_client_secret
CRITICAL: Always use .env files. Never hardcode credentials. Always add .env to .gitignore.
Standard Connection
import os
from dotenv import load_dotenv
from arcgis.gis import GIS
load_dotenv()
gis = GIS(
os.getenv("ARCGIS_ORG_URL"),
os.getenv("ARCGIS_USERNAME"),
os.getenv("ARCGIS_PASSWORD")
)
print(f"Connected as: {gis.users.me.username}")
Connection Health Check
def check_connection(gis):
me = gis.users.me
return {
"connected": True,
"username": me.username,
"role": me.role,
"org": gis.properties.name,
"org_id": gis.properties.id
}
Red Cross Specific Notes
- Enterprise org URL:
https://arc-nhq-gis.maps.arcgis.com - Org ID:
pGfbNJoYypmNq86F - Service base:
https://services.arcgis.com/pGfbNJoYypmNq86F/arcgis/rest/services/ - User's AGOL username:
ARC_Jeff.Franzen_825009 - User's role:
org_admin/RedCross_Admin - Test against personal org first before running on enterprise
Common Gotchas
- Client credentials tokens (2-legged) CANNOT access org-private content — always returns 403
- Temporary tokens from app settings page are 2-legged — same limitation
- Token format matters — tokens may end with
.which must be preserved - Token expiration — default is 30min-2hrs depending on method; plan accordingly
- MFA orgs — if org requires MFA, OAuth is required (username/password won't work)
- Rate limits — enterprise orgs may throttle; add delays for bulk operations
Related Skills
- arcgis-content-ops
- arcgis-feature-layer-ops
- arcgis-python
- arcgis-notebooks
