Skip to content
getgeolens.com

OAuth/OIDC Setup

GeoLens supports SSO via OAuth 2.0 / OpenID Connect with four provider types: Google, GitHub, Microsoft Entra ID, and generic OIDC (Auth0, Okta, Keycloak, Authentik, and any other OIDC-compliant IdP). GitHub is a plain OAuth 2.0 provider rather than OIDC, so it does not use a discovery document. All flows use PKCE (Proof Key for Code Exchange, S256) automatically. Client secrets are encrypted at rest (see PKCE & client-secret encryption).

Replace https://geolens.example.com with your GeoLens instance’s URL in every example below.

OAuth ProviderGeoLens APIBrowserOAuth ProviderGeoLens APIBrowserUserClick "Sign in with Google"GET /api/auth/oauth/google/login302 redirect to provider authorize endpoint (PKCE S256)GET /authorize?client_id=...&code_challenge=...&state=...Authentication + consentApprove302 redirect to /api/auth/oauth/google/callback?code=...GET /api/auth/oauth/google/callback?code=...POST /token (exchange code, PKCE verify)id_token + access_tokenValidate id_token, find or create user (link by email match)302 redirect to /oauth/callbackPOST /api/auth/oauth/exchange/ (one-time code)Access token + httpOnly refresh cookieUser

The diagram shows the bundled setup, where the web app and the API share an origin. The callback hands the browser a one-time code that expires after two minutes and works only in the browser that started the sign-in, and the exchange sets the refresh token as an httpOnly cookie. When the web app and the API are on different origins, the cookie can’t be used, so the callback redirects to /oauth/callback#token=...&refresh_token=... with both tokens in the URL fragment instead.

End users do not call these endpoints directly. They sign in through the GeoLens web UI, which performs the authorization-code flow on their behalf. Once the UI has a JWT, machine clients reuse it like any other Bearer token (see API authentication).

PKCE (Proof Key for Code Exchange) is enforced for every OAuth flow regardless of provider type: GeoLens generates a random code_verifier, sends the SHA-256 code_challenge on the authorize redirect, and verifies the verifier on the token exchange. This protects against authorization-code interception even on browsers without HTTPS-only contexts.

Client secrets stored in the database are encrypted at rest with Fernet (AES-128-CBC + HMAC-SHA256). The key is SECRET_ENCRYPTION_KEY when it is set, with SECRET_ENCRYPTION_KEY_PREVIOUS still accepted for reads during a key rotation. When it isn’t set, the key is derived from JWT_SECRET_KEY, and rotating the JWT secret then makes every stored client secret unreadable until you re-enter it in the admin UI. Set SECRET_ENCRYPTION_KEY so the JWT secret can rotate without touching SSO. After adding it to an existing install, re-encrypt the stored secrets with backend/scripts/rotate_secrets.py; RUNBOOK.md section 11 has the steps for adopting and rotating the key.

The simplest way to configure OAuth is through the admin settings panel:

  1. Navigate to Admin -> Settings -> Auth.
  2. Scroll to the OAuth Providers section.
  3. Click Add Provider.
  4. Select the provider type, fill in the required fields, and click Create Provider.

Providers can be enabled, disabled, edited, or deleted from the same panel. The login screen reflects changes immediately. No restart required.

Providers are records in the database, not settings, so they stay manageable here and through the provider API when ENV_ONLY_CONFIG=true. That mode locks the settings on the Auth tab to their environment values but does not lock providers. There is no environment variable or file format for declaring providers, so an env-only deployment still creates them in the admin UI or API. Before 1.24.0 the tab disabled Add, Edit and Delete in env-only mode even though the API accepted them.

Whether you use the admin UI or the API, every provider has the same configurable fields:

FieldRequiredDescription
slugYesURL-safe identifier used in callback URLs (e.g. google, azure-ad)
display_nameYesLabel shown on the login page button
provider_typeYesOne of: google, github, microsoft, oidc
client_idYesOAuth client ID from the IdP
client_secretYesOAuth client secret
discovery_urlNoOIDC discovery URL (.well-known/openid-configuration). Auto-populates for Google and Microsoft. Rejected for github (GitHub is not OIDC)
authorize_urlNoAuthorization endpoint (only needed if discovery_url is not set; auto-filled for github)
token_urlNoToken endpoint (only needed if discovery_url is not set; auto-filled for github)
userinfo_urlNoUserinfo endpoint (only needed if discovery_url is not set; auto-filled for github)
scopesNoSpace-separated scopes. Default: openid profile email (for github, read:user user:email)
default_roleNoRole assigned to new users: viewer, editor, or admin. Default: viewer
enabledNoWhether the provider appears on the login page. Default: true
  1. Go to the Google Cloud Console.
  2. Create a new OAuth 2.0 Client ID (application type: Web application).
  3. Add the authorized redirect URI:
    https://geolens.example.com/api/auth/oauth/google/callback
  4. Copy the Client ID and Client Secret.
  5. In GeoLens, create the provider via the admin UI (Admin -> Settings -> Auth -> Add Provider -> Google), or via the API:
Terminal window
curl -X POST https://geolens.example.com/api/settings/oauth-providers/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "google",
"display_name": "Google",
"provider_type": "google",
"client_id": "123456789.apps.googleusercontent.com",
"client_secret": "GOCSPX-xxxxxxxxxxxx",
"discovery_url": "https://accounts.google.com/.well-known/openid-configuration",
"scopes": "openid profile email",
"default_role": "viewer",
"enabled": true
}'

The “Sign in with Google” button appears on the login page immediately after creation.

Providers can be managed entirely via the API. All endpoints require the manage_settings capability (admin role). Obtain a JWT token first. The admin password is generated at install time and stored in your .env as GEOLENS_ADMIN_PASSWORD — the installer never prints the value itself, so read it back from that file (or substitute your own admin account):

Terminal window
export GEOLENS_ADMIN_USERNAME="$(grep '^GEOLENS_ADMIN_USERNAME=' .env | cut -d= -f2-)"
export GEOLENS_ADMIN_PASSWORD="$(grep '^GEOLENS_ADMIN_PASSWORD=' .env | cut -d= -f2-)"
TOKEN=$(curl -s -X POST https://geolens.example.com/api/auth/login \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "username=$GEOLENS_ADMIN_USERNAME" \
--data-urlencode "password=$GEOLENS_ADMIN_PASSWORD" | jq -r '.access_token')
Terminal window
curl https://geolens.example.com/api/settings/oauth-providers/ \
-H "Authorization: Bearer $TOKEN"

The response never includes the client_secret. Secrets are write-only.

Terminal window
curl -X PUT https://geolens.example.com/api/settings/oauth-providers/{provider_id} \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'

Only include client_secret in the update payload if you need to rotate it. Omitting the field preserves the existing encrypted secret.

Terminal window
curl -X DELETE https://geolens.example.com/api/settings/oauth-providers/{provider_id} \
-H "Authorization: Bearer $TOKEN"

Deleting a provider also removes all linked OAuth accounts for that provider. Affected users fall back to local-password login (if they have a password set) or lose access entirely.

One case is refused: if password login is disabled and this is the last enabled SSO provider, the delete returns 422 rather than locking everyone out. The same guard fires on an update that sets enabled: false. Re-enable password login, or enable another provider, first.

If Allowed Email Domains is set on the Auth tab, it is checked before any of the linking rules below. For a first-time SSO identity a non-empty allowlist makes a verified email mandatory: an identity with no email claim, an unverified one, or one outside an allowed domain is refused rather than provisioned. Returning users are checked too — against the IdP’s verified claim when present, otherwise their stored address — with one exemption: a returning user who holds manage_settings is let through, mirroring the break-glass on password login. The list is empty by default, which allows any domain.

When a user signs in via OAuth, GeoLens applies these rules in order:

  1. Existing OAuth link. If the user has previously signed in with the same provider and subject ID, they are logged in to the existing account.
  2. Email match — verified addresses only. If no prior OAuth link exists, the OAuth identity is linked to a local user with the same email address (case-insensitive) only when the IdP asserted email_verified: true. The local password remains valid; the user can use either authentication method. If the claim is missing or false and a local account already holds that address, the login is refused — sign in with the local account and link OAuth from your profile, or have the IdP verify the address. If the claim is missing or false and no local account holds that address, the email is discarded and the account created in step 3 has none.
  3. New account. Otherwise, and only while self-registration (REGISTRATION_ENABLED) is on, a new active user account is created with a username derived from the email prefix or display name. The role assigned is the provider’s default_role. With self-registration off the sign-in is refused; an admin creates the account first, and the next SSO sign-in links to it through step 2. With it on, set Allowed Email Domains or use a single-tenant identity provider, because a Google, GitHub or multi-tenant Microsoft provider otherwise lets anyone with such an account create an active account here.

This means existing local users can start using OAuth without losing their data, as long as the email addresses match. For organizations migrating from local-only auth, the recommended sequence is: enable the OAuth provider with default_role: viewer, instruct users to sign in via OAuth once (which auto-links by email — this requires an IdP that emits email_verified; Google, Microsoft, Okta, and Auth0 all do, and the GitHub provider satisfies it by construction, since GeoLens accepts only a GitHub account’s primary and verified address), then optionally disable local-password login for OAuth-managed accounts.