Skip to content

Feature: integrate Hanko auth login #538

Open
andrea-chirillano wants to merge 32 commits into
mainfrom
feature/login-hanko
Open

Feature: integrate Hanko auth login #538
andrea-chirillano wants to merge 32 commits into
mainfrom
feature/login-hanko

Conversation

@andrea-chirillano

@andrea-chirillano andrea-chirillano commented Mar 26, 2026

Copy link
Copy Markdown
Collaborator

This pull request requires in-depth review before merging.

This PR includes changes to the backend (Django), frontend (React/JS), infrastructure (Docker, nginx), and tests. Review carefully before approving.


Add Hanko SSO Authentication

Integrates Hanko SSO as an alternative to legacy OSM OAuth, enabling single sign-on across the HOT ecosystem via login.hotosm.org.

Key changes

  • New AUTH_PROVIDER setting (legacy | hanko) — default is legacy, existing deployments are unaffected
  • HankoAuthentication DRF backend added to DEFAULT_AUTHENTICATION_CLASSES (takes priority, falls back to token/OAuth2)
  • Onboarding flow to link existing OSM accounts or create new ones for first-time Hanko users
  • hotosm-auth web component (<hotosm-auth>) rendered in NavBar via HankoAuthButton.js
  • <hotosm/tool-menu> web component added to the layout (loaded from jsDelivr, fixed version)
  • Django upgraded from 3.2 → 4.2 LTS (required by hotosm-auth[django])
  • print() calls replaced with logging throughout api/views.py
  • Responsive navbar collapse added (mobile support)
  • Admin authorization now supports both modes: is_superuser (legacy) and ADMIN_EMAILS env var (Hanko)

New API Endpoints

Endpoint Method Description
/api/auth/me/ GET Current user info
/api/v1/auth/status/ GET Auth status + onboarding flag
/api/v1/auth/onboarding/ GET Callback from login service after OSM link
/api/admin/ Hanko→Django user mapping (admin panel)
/api/v1/ Hanko OSM OAuth routes (from hotosm_auth_django)

Legacy OAuth routes (/osm/, /o/, /authorized) are only registered when AUTH_PROVIDER=legacy.


New Dependencies

Package Location Notes
hotosm-auth[django]==0.2.12 Backend (PyPI) Hanko middleware, helpers, OSM views
tzdata Backend (PyPI) Required on minimal images (Python 3.9+)
@hotosm/hanko-auth@0.5.2 Frontend (jsDelivr CDN) Login web component
@hotosm/tool-menu@0.2.6 Frontend (jsDelivr CDN) HOT ecosystem nav menu

Required Environment Variables

Backend — required (Hanko mode)

Variable Required Example Description
AUTH_PROVIDER Yes hanko Set to hanko to enable SSO. Default: legacy
HANKO_API_URL Yes (Hanko) https://login.hotosm.org Hanko service URL (server-to-server)
COOKIE_SECRET Yes (Hanko) <shared-secret> 32+ bytes. Must match the login service and other apps — coordinate with login team

Backend — recommended

Variable Default Description
HANKO_PUBLIC_URL same as HANKO_API_URL Public URL the browser sees / used for the login-service redirect
COOKIE_DOMAIN inferred Cookie domain — use .hotosm.org for cross-subdomain SSO
COOKIE_SECURE not DEBUG true in production
ADMIN_EMAILS "" Comma-separated list; grants is_superuser in Hanko mode
RAW_DATA_API_PUBLIC_URL falls back to RAW_DATA_API_URL Public Raw Data API URL for frontend

Backend — optional (OSM connection & JWT)

These are read directly from the environment by the hotosm-auth library.

Variable Default Description
OSM_CLIENT_ID / OSM_CLIENT_SECRET unset Enable starting the OSM OAuth flow from the export-tool itself (/api/v1/auth/osm/login/). If unset, onboarding redirects users to the login service to connect OSM
OSM_REDIRECT_URI unset OSM OAuth callback URL when OSM is enabled locally
JWT_AUDIENCE None Optional validation of the JWT aud claim
JWT_ISSUER inferred from HANKO_API_URL JWT issuer; set explicitly if Hanko runs behind a proxy
COOKIE_SAMESITE lax SameSite policy for the auth cookie

Frontend (injected via Django template into window.*)

Variable Required Description
AUTH_PROVIDER Yes Must match backend
HANKO_URL Yes (Hanko) Hanko public URL — passed as hanko-url to <hotosm-auth>

Database Migrations

When AUTH_PROVIDER=hanko, the hotosm_auth_django app is added to INSTALLED_APPS and contributes one migration (0001_initial):

  • Creates the hanko_user_mappings table (maps Hanko user IDs ↔ Django user IDs, scoped by app_name)
  • Uses CREATE TABLE IF NOT EXISTS — idempotent, safe to re-run
  • Run with: python manage.py migrate (or python manage.py migrate hotosm_auth_django)

No migrations are added to the export-tool's own apps (api, jobs, tasks, ui). Rolling back to AUTH_PROVIDER=legacy needs no reverse migration — the table is harmless if left in place. Explicit rollback: python manage.py migrate hotosm_auth_django zero.


How It Works

Legacy mode (default)

AUTH_PROVIDER=legacy — no changes, continues using OSM OAuth with access-token header.

Hanko mode

  1. User clicks login → redirected to login.hotosm.org
  2. Hanko sets a JWT cookie after authentication
  3. HankoAuthentication DRF backend validates the JWT cookie via hotosm_auth_django
  4. If a Django↔Hanko mapping exists → user is authenticated
  5. If no mapping → GET /api/v1/auth/status/ returns needs_onboarding: true → onboarding flow starts
  6. Onboarding: user chooses to link an existing OSM account (recovers data) or create a new one

Onboarding account matching

For "I already have an account", existing users are matched by priority:

  1. OSM ID — via social_auth_usersocialauth (find_legacy_user_by_osm_id)
  2. Email — Hanko email vs auth_user.email (find_legacy_user_by_email, lowest id wins)

If neither matches, the user is redirected back to the login service with an error and no DB rows are created.


Test Plan

  • Legacy auth continues working with AUTH_PROVIDER=legacy
  • Hanko login/logout flow works end-to-end
  • New user onboarding creates a Django account
  • Existing user onboarding recovers account via OSM link
  • GET /api/v1/auth/status/ returns correct authenticated and needs_onboarding values
  • Navbar shows correct user state in both modes
  • Protected routes redirect to login when unauthenticated
  • Worker dashboard access control works in both modes
  • Export creation (POST /api/v1/exports/) works with Hanko JWT cookie
  • Django 4.2 migration runs cleanly — no regressions from 3.2 upgrade
  • hotosm_auth_django app migrations apply correctly when AUTH_PROVIDER=hanko

Backward Compatibility

  • Default is legacy — no action required for existing deployments
  • Existing OSM OAuth users continue working unchanged
  • Switch to hanko when ready by setting the environment variables above

Deployment

The deploy is done manually via SSH. There is no automated pipeline — after the PR is merged, someone with server access must connect and deploy by hand.


Dependency & Django Version Update

Django has been upgraded from 3.2 → 4.2 LTS and several dependencies have been
updated to their latest compatible versions. These changes are required for two reasons:

  • Unit tests: the updated test suite relies on APIs and behaviors introduced in
    Django 4.2. Running tests against Django 3.2 will result in failures.
  • Security: Django 3.2 no longer receives security
    patches. Upgrading to the 4.2 LTS branch ensures continued security support.

Collateral Fixes (not Hanko, but required)

A few changes in this PR are not part of the SSO feature itself, but were necessary to make the branch run, build under Docker, or pass tests. Listed here so reviewers aren't surprised by non-Hanko diffs:

Change File(s) Why it was needed
Removed userinfo= from Galaxy(...) calls tasks/task_runners.py The pinned osm-export-tool-python==2.0.10 does not accept a userinfo argument — main raises TypeError on every export run
orand in the last_run_status guard tasks/task_runners.py The original != "SUBMITTED" or != "RUNNING" is always True, so a new run was created even when one was already SUBMITTED/RUNNING, allowing duplicate parallel runs. The matching else branch was unreachable dead code
Redis host/port read from env vars tasks/task_runners.py, core/settings/project.py Redis was hardcoded to localhost; in a containerized setup it runs in a separate container. Defaults to localhost, so non-Docker setups are unaffected
FeatureSelection.example()SIMPLE_FEATURE_SELECTION constant conftest.py (new) + api/tests/test_views.py, jobs/tests/test_models.py, tasks/tests/test_models.py The import from feature_selection.feature_selection import FeatureSelection raises ModuleNotFoundError — the affected test files in main cannot even be collected
Added [tool:pytest] config setup.cfg Sets DJANGO_SETTINGS_MODULE so the test suite can be run with pytest

@emi420
emi420 marked this pull request as draft March 27, 2026 17:02

@emi420 emi420 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andrea-chirillano ! I did my review before moving the PR to "Ready to review" so it's easier for other people to review it.

Thanks!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andrea-chirillano ! please remove this file, the branch feature/login_hanko will be deleted once merged so this file will not be used after that.

@andrea-chirillano andrea-chirillano Mar 31, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, the branch is necessary because that branch depends on the staging environment https://export.testlogin.hotosm.org/v3/

Comment thread api/tests/test_hanko_auth.py Outdated
Comment thread api/views.py
Comment thread api/views.py

if not all:
queryset = queryset.filter(Q(user_id=user.id))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? it adds additional filters, could we revert it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also realized they were unnecessary, and I've already reverted those changes.

Comment thread api/views.py Outdated
Comment thread ui/views.py Outdated
Comment thread ui/views.py Outdated
Comment thread Makefile Outdated
Comment thread nginx.conf

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file, it's only for testing on export.testlogin.hotosm.org and will not be used after the PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it to gitignore

Comment thread docker/Dockerfile.dev

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is for development right? if that's the case, please add a comment about it and move it to a docker folder

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it to gitignore and move that Dockerfile into a docker folder.

@spwoodcock
spwoodcock self-requested a review April 10, 2026 20:24
@spwoodcock

Copy link
Copy Markdown
Member

Thanks - I just saw this PR from the message on slack!

I have no idea how Export Tool is deployed in production, nor how we can test this PR for now, but once it's marked as ready for review, I can look into those things with dk 😄

@kshitijrajsharma

Copy link
Copy Markdown
Member

Thanks - I just saw this PR from the message on slack!

I have no idea how Export Tool is deployed in production, nor how we can test this PR for now, but once it's marked as ready for review, I can look into those things with dk 😄

its in a single ec2 instance in our aws ! Ideally we can swamp a quick stage server for export tool in a tiny instance ! I am happy to review once PR is ready !

@spwoodcock

Copy link
Copy Markdown
Member

Is it a systemd service or something, running the Python / Django server? I can't see any containerisation in the repo

@spwoodcock

Copy link
Copy Markdown
Member

Ah I literally just saw this dir!

https://github.com/hotosm/osm-export-tool/tree/main/ops/systemd

Assuming a few services all run in parallel, managed by systemd

@emi420

emi420 commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator

We have a testing deployment:

https://export.testlogin.hotosm.org/

@kshitijrajsharma

kshitijrajsharma commented Apr 10, 2026

Copy link
Copy Markdown
Member

Yes systemd services, export tool isn't containerized yet ! Yarn builds the frontend !

@hg1g
hg1g marked this pull request as ready for review May 15, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants