Skip to content
getgeolens.com

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.

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.

Terminal window
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.

Terminal window
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.

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.

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 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):

Terminal window
curl https://geolens.example.com/api/collections/datasets/items \
-H "X-Api-Key: <your-api-key>"

Query-string form:

Terminal window
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.

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:

  1. User clicks “Sign in with <provider>” in the GeoLens UI.
  2. UI redirects to https://geolens.example.com/api/auth/oauth/<provider>/login.
  3. Provider authenticates the user and calls back to https://geolens.example.com/api/auth/oauth/<provider>/callback.
  4. GeoLens issues a JWT and the UI stores it.

Once the UI has a JWT, machine clients reuse it like any other Bearer token:

Terminal window
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.

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.