API Authentication
The GeoLens REST API supports three authentication methods. Pick the one that matches your client:
- JWT Bearer tokens: short-lived, good for end-user apps and the web UI.
- API keys: long-lived, good for scripts, dashboards, and machine clients.
- OAuth/OIDC: admin-configured, good for SSO via Google, GitHub, Microsoft, or generic OIDC providers.
Resolution order on every request: X-Api-Key header -> ?api_key= query
parameter (reads only, from v1.18.1) -> Authorization: Bearer <jwt> header ->
anonymous. The first match
wins.
Replace https://geolens.example.com with your GeoLens instance’s URL in every
example below.
JWT bearer tokens
Section titled “JWT bearer tokens”For interactive end-user clients, exchange a username and password for a
short-lived access token plus a long-lived refresh token, then send the access
token as Authorization: Bearer <jwt> on each subsequent request.
Step 1: log in.
curl -X POST https://geolens.example.com/api/auth/login \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=alice&password=hunter2"Response shape:
{ "access_token": "eyJhbGc...", "refresh_token": "...", "token_type": "bearer", "expires_in": 900}expires_in is the number of seconds until the access token expires — schedule
your refresh from it. refresh_token is null when the caller opted into the
browser cookie flow described below.
Step 2: use the access token.
curl https://geolens.example.com/api/collections/datasets/items \ -H "Authorization: Bearer eyJhbGc..."Refresh tokens. Access tokens are short-lived. When yours expires, exchange
the refresh token for a new pair via POST /api/auth/refresh/. Refresh tokens
rotate on every call, so store the new one and discard the old.
Where to store tokens. In the browser, keep the access token in memory
(not localStorage) and let GeoLens hold the refresh token for you: send
X-GeoLens-Auth-Mode: cookie on POST /api/auth/login and the server returns
the refresh token as an httpOnly cookie with a null refresh_token in the
body. Login also sets a second cookie, geolens_csrf, which is deliberately
not httpOnly so your script can read it. POST /api/auth/refresh/ with the
same header reads the refresh cookie and requires you to echo that value back
in an X-CSRF-Token header — the double-submit check compares the two and
answers 403 when they disagree. Programmatic callers (CLI,
SDKs, CI) omit the header and keep the body token. For server-side scripts,
treat both like any other secret: environment variables or a secrets manager,
never committed to source control.
Signing out
Section titled “Signing out”POST /api/auth/logout/ revokes the account’s access and refresh tokens across
all devices. The web UI’s Sign out action uses this endpoint. API keys
remain valid; revoke them separately when needed.
Session isolation (unreleased)
Section titled “Session isolation (unreleased)”The development branch adds POST /api/auth/logout/session/ to revoke the
refresh tokens from one login without signing out other devices. Access tokens
from that login remain valid until their normal expiry. This endpoint is not
included in the latest release.
In the same upcoming release, reusing a rotated refresh token after the concurrency grace period, while that token is still within its original lifetime, revokes only that login’s refresh tokens. Keep the new token after each refresh; a client affected by replay rejection must sign in again. Account-wide logout and password-reset revocation remain unchanged.
API keys
Section titled “API keys”API keys are long-lived credentials suitable for scripts, scheduled jobs, and
machine clients. They can be passed two ways. The header form is preferred.
The query-string form exists for clients that can only be handed a bare URL
and is deprecated elsewhere because a key in a URL lands in access and proxy
logs. Most tools can set the header —
ogr2ogr and ogrinfo take
--config GDAL_HTTP_HEADERS "X-Api-Key: <your-api-key>". For which credential
fits which client, and how each example sets the header (MapLibre
transformRequest, the ArcGIS JS request interceptor, QGIS’s API Header
authentication configuration, a DuckDB HTTP secret), start from the
examples repo’s authentication section
and the per-example READMEs.
Header form (preferred):
curl https://geolens.example.com/api/collections/datasets/items \ -H "X-Api-Key: <your-api-key>"Query-string form:
curl 'https://geolens.example.com/api/collections/datasets/items?api_key=<your-api-key>'From v1.18.1 the query-string form authenticates only GET, HEAD and
OPTIONS requests, minus the few GETs that write (the same ones a
read_only key is refused). On anything else, POST /api/stac/search and
POST /api/query/ included, a key in the query string is treated as absent,
so a request that relied on it answers as if unauthenticated. Move such clients
to the X-Api-Key header.
The header takes precedence over the query string. Never combine the two on the same request.
A key the server cannot resolve (revoked or mistyped) returns 401 on every endpoint that reads credentials from v1.14.0. Older releases discarded an unknown key on most routes and answered 200 with the public subset, which looks exactly like a smaller catalog, so a script whose key had lapsed kept working against less data. Requests that send no credential still get the public view.
Three cases sit outside that rule. POST /api/auth/logout/ accepts a dead
access token so a stale session can still be cleared. A request that a valid
X-Embed-Token or a valid signed tile template (sig, exp, scope) has
already authorized is served and the dead key is ignored (an invalid or
missing one puts the request back under the rule). And
GET /api/maps/shared/{token} answers 404 for an unknown link and 410 for a
revoked one whatever you send. The
API reference overview carries the full statement.
Obtaining a key. Any signed-in user creates their own keys under
Settings > API Keys in the web UI (POST /api/auth/api-keys/) — see
Account settings -> API Keys for that form,
what the reveal dialog does, and how the list reads. Admins can
also issue and revoke keys for other users from the user-management page
(POST /api/admin/api-keys/); see
User management & RBAC. Each key belongs to exactly one
user account, and what it can do is that user’s permissions narrowed by the
key’s own scope.
Key scope. Every key is minted full or read_only. The scope field on
the create request sets it and defaults to "full"; both mint forms in the UI
offer the same Full access / Read only picker, also defaulting to full.
A full key acts as the owner, with exactly the owner’s permissions. A
read_only key authenticates GET, HEAD, and OPTIONS only. Two POST
routes are carve-outs because both are reads that happen to be POSTs:
POST /api/query/ (the read-only SQL sandbox) and POST /api/stac/search. One
GET is refused despite its method — GET /api/datasets/{id}/validate/?refresh=true,
which recomputes and persists a quality score; the same route without refresh,
or with a false-y value, serves the cached result normally. Scope is fixed at
creation, so changing it means minting a new key and revoking the old one. Pick
read_only for dashboards, CI checks, and anything that only ever fetches.
Anything else a read_only key asks for is refused while the key is being
resolved, before the handler runs, with 403 and an RFC 7807 body:
{ "type": "about:blank", "title": "Forbidden", "status": 403, "detail": "This API key is read-only"}Security. API keys do not expire unless created with expires_at through
the API, but they are invalidated by security events on the owner’s account: a
password change, a role change, a conversion between auth providers, or an
admin approving a pending account all stop every existing key for that user
from resolving. Signing out of the web UI deliberately does not. Rotate keys on
a schedule and revoke any key whose host environment changes. Treat a leaked
API key the same as a leaked password.
OAuth / OIDC
Section titled “OAuth / OIDC”GeoLens supports OAuth/OIDC sign-in via Google, GitHub, Microsoft Entra ID, and any generic OIDC provider, configured by an administrator. (GitHub is plain OAuth 2.0 rather than OIDC; OAuth/OIDC setup covers the difference.) End users do not call the OAuth endpoints directly; they sign in through the GeoLens web UI, which performs the standard authorization-code flow on their behalf.
The browser flow:
- User clicks “Sign in with
<provider>” in the GeoLens UI. - UI redirects to
https://geolens.example.com/api/auth/oauth/<provider>/login. - Provider authenticates the user and calls back to
https://geolens.example.com/api/auth/oauth/<provider>/callback. - GeoLens issues a JWT and the UI stores it.
Once the UI has a JWT, machine clients reuse it like any other Bearer token:
curl https://geolens.example.com/api/collections/datasets/items \ -H "Authorization: Bearer eyJhbGc..."Provider setup. OIDC client registration and the list of supported providers are admin-configured. See OAuth/OIDC setup.
Out of scope. GeoLens does not currently support OIDC client-credentials or PKCE machine flows. Machine-to-machine clients should use API keys.
Browser clients and CORS
Section titled “Browser clients and CORS”Anonymous requests to the standards routes and, from v1.14.1, to the anonymous
catalog-search routes GET /api/search/datasets/ and GET /api/search/facets/
get Access-Control-Allow-Origin: *, so a page on another origin can read the
public catalog with no setup. As soon as one of those requests carries a
credential (X-Api-Key, Authorization, a cookie, or ?api_key=) the wildcard
is gone: the page’s exact origin has to be listed in CORS_ALLOWED_ORIGINS, and
a literal * there is rejected. See
Settings -> Network.
Tile routes work differently. Vector and raster tiles always answer with a
static Access-Control-Allow-Origin: *, credential or not, because a tile URL
template carries its authorization in the query string (sig, exp, scope,
or the deprecated ?api_key=) and the response is cacheable. A cross-origin
page can therefore draw a signed private tile with no CORS_ALLOWED_ORIGINS
entry — but a tile fetch that sets X-Api-Key as a header is preflighted and
does still need one.
Raster tiles began sending CORS headers in v1.13.0. That matters for loaders
that fetch tiles with WebGL or fetch (MapLibre, the
ArcGIS Maps SDK for JavaScript, OpenLayers with crossOrigin set), which
draw an empty map against an older instance while the server returns valid
PNGs. <img>-based loaders (Leaflet, OpenLayers by default) render the same
tiles either way.