@@ -18,7 +18,7 @@ Step by step guide to integrate `hotosm-auth` in your project.
1818```
1919Does your app have an existing auth system (legacy)?
2020│
21- ├─ NO → Simple Integration (Portal, OAM )
21+ ├─ NO → Simple Integration (Portal)
2222│ You only need to validate Hanko JWT
2323│
2424└─ YES → Integration with Mapping (Drone-TM, fAIr)
@@ -29,7 +29,7 @@ Does your app have an existing auth system (legacy)?
2929
3030## FastAPI: Simple Integration
3131
32- For apps ** without legacy auth** (e.g.: Portal, OAM ).
32+ For apps ** without legacy auth** (e.g.: Portal).
3333
3434### Step 1: Dependency
3535
@@ -196,6 +196,14 @@ MIDDLEWARE = [
196196
197197``` python
198198# views.py
199+ from hotosm_auth_django import login_required
200+
201+ # Decorator approach
202+ @login_required
203+ def my_view (request ):
204+ return JsonResponse({" email" : request.hotosm.user.email})
205+
206+ # Class-based view — check manually
199207class ProtectedView (APIView ):
200208 def get (self , request ):
201209 if not request.hotosm.user:
@@ -207,9 +215,12 @@ class ProtectedView(APIView):
207215
208216## Django: Integration with Mapping
209217
218+ ### Steps 1-2: Same as Simple
219+
210220### Step 3: Settings with dual-mode
211221
212222``` python
223+ # settings.py
213224AUTH_PROVIDER = env(" AUTH_PROVIDER" , default = " legacy" )
214225
215226if AUTH_PROVIDER == " hanko" :
@@ -220,14 +231,59 @@ if AUTH_PROVIDER == "hanko":
220231 )
221232```
222233
223- ### Step 4: Admin routes
234+ ### Step 4: DRF authentication backend
235+
236+ The middleware populates ` request.hotosm.user ` . Create a DRF ` BaseAuthentication ` class that maps that Hanko user to your app user:
237+
238+ ``` python
239+ # authentication.py
240+ from rest_framework import authentication
241+ from hotosm_auth_django import get_mapped_user_id
242+ from myapp.models import AppUser
243+
244+ class HankoAuthentication (authentication .BaseAuthentication ):
245+ def authenticate (self , request ):
246+ hanko_user = getattr (request, ' hotosm' , None ) and request.hotosm.user
247+ if not hanko_user:
248+ return None , None
249+
250+ app_user_id = get_mapped_user_id(hanko_user, app_name = " my-app" )
251+
252+ if app_user_id:
253+ user = AppUser.objects.get(id = app_user_id)
254+ return user, None
255+
256+ # No mapping yet → send to onboarding
257+ request.needs_onboarding = True
258+ return None , None
259+
260+
261+ # Select auth class based on provider
262+ if settings.AUTH_PROVIDER == " hanko" :
263+ MyAuthentication = HankoAuthentication
264+ else :
265+ MyAuthentication = LegacyAuthentication # your existing class
266+ ```
267+
268+ Register it in settings:
269+
270+ ``` python
271+ # settings.py
272+ REST_FRAMEWORK = {
273+ " DEFAULT_AUTHENTICATION_CLASSES" : [
274+ " myapp.authentication.MyAuthentication" ,
275+ ]
276+ }
277+ ```
278+
279+ ### Step 5: Admin routes
224280
225281``` python
226282# urls.py
227- if getattr ( settings, ' AUTH_PROVIDER' , ' legacy ' ) == ' hanko' :
283+ if settings. AUTH_PROVIDER == " hanko" :
228284 from hotosm_auth_django.admin_routes import create_admin_urlpatterns
229285 admin_patterns = create_admin_urlpatterns(
230- app_name = " my-app" , user_model = " myapp.User " ,
286+ app_name = " my-app" , user_model = " myapp.AppUser " ,
231287 user_id_column = " id" , user_name_column = " username" , user_email_column = " email" ,
232288 )
233289 urlpatterns += [path(" api/admin/" , include(admin_patterns))]
@@ -237,7 +293,7 @@ if getattr(settings, 'AUTH_PROVIDER', 'legacy') == 'hanko':
237293
238294## Litestar: Simple Integration
239295
240- For apps ** without legacy auth** (e.g.: Field-TM) .
296+ For apps ** without legacy auth** .
241297
242298### Step 1: Dependency
243299
@@ -256,9 +312,10 @@ from litestar import Litestar
256312from hotosm_auth_litestar import setup_auth
257313
258314# setup_auth() loads config from env, returns (deps, route_handlers)
315+ # route_handlers includes the OSM OAuth routes — spread alongside your own handlers
259316deps, route_handlers = setup_auth()
260317
261- app = Litestar(route_handlers = route_handlers, dependencies = deps)
318+ app = Litestar(route_handlers = [ * route_handlers, me, ... ] , dependencies = deps)
262319```
263320
264321### Step 3: Protect routes
@@ -301,6 +358,7 @@ OSM_CLIENT_SECRET=your-client-secret
301358# auth_deps.py
302359from litestar import Request
303360from hotosm_auth_litestar import get_current_user, get_mapped_user_id
361+ from app.hanko_helpers import lookup_user_by_email, create_app_user
304362
305363async def login_required (request : Request):
306364 hanko_user = await get_current_user(request)
@@ -316,7 +374,29 @@ async def login_required(request: Request):
316374 return await get_user_by_id(db, user_id)
317375```
318376
319- ### Step 4: Admin routes (optional)
377+ ### Step 4: Helper functions
378+
379+ ``` python
380+ # hanko_helpers.py
381+ async def lookup_user_by_email (db , email : str ) -> Optional[str ]:
382+ """ Look up user by email. Returns user_id or None."""
383+ async with db.cursor() as cur:
384+ await cur.execute(" SELECT id FROM users WHERE email = %s " , [email])
385+ row = await cur.fetchone()
386+ return str (row[0 ]) if row else None
387+
388+ async def create_app_user (db , hanko_user : HankoUser) -> str :
389+ """ Create new user. Returns user_id."""
390+ async with db.cursor() as cur:
391+ await cur.execute(
392+ " INSERT INTO users (email, name) VALUES (%s , %s ) RETURNING id" ,
393+ [hanko_user.email, hanko_user.username or hanko_user.email.split(" @" )[0 ]]
394+ )
395+ row = await cur.fetchone()
396+ return str (row[0 ])
397+ ```
398+
399+ ### Step 5: Admin routes (optional)
320400
321401``` python
322402# main.py
@@ -357,7 +437,7 @@ VITE_HANKO_URL=https://login.hotosm.org
357437| ------| ----------------| -----------------| ---------------| ----------------| -----------------| ------------------|
358438| Dependency | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
359439| Init | init_auth | init_auth | middleware | middleware | setup_auth() | setup_auth() |
360- | Protect routes | CurrentUser | Override login_required | request.hotosm | request.hotosm | AuthContext | Custom dep |
361- | Helper functions | - | ✓ | - | ✓ | - | ✓ |
362- | Admin routes | - | Optional | - | Optional | - | Optional |
440+ | Protect routes | CurrentUser | Override login_required | request.hotosm | BaseAuthentication | AuthContext | Custom dep |
441+ | Helper functions | - | ✓ | - | - | - | ✓ |
442+ | Admin routes | - | Step 5 | - | Step 5 | - | Step 5 |
363443| AUTH_PROVIDER env | - | ✓ | - | ✓ | - | ✓ |
0 commit comments