Skip to content

bug: Python SDK with Django session async issue #22

Open
@liam-pulsation

Description

@liam-pulsation

Python SDK with Django session async issue

Having recently discovered logto.io, we tried to implement it in Django.

We tried using TraditionalWebApp process to protect our views, following your official Flask example.

Implementation

First, I implemented /signin and /callback routes as shown in the tutorial, wrapping the logto package logic with async_to_sync decorators.

To follow the tutorial, I used session out of views using Django ref : https://docs.djangoproject.com/en/5.0/topics/http/sessions/#using-sessions-out-of-views

views.py

from logto import LogtoClient, LogtoConfig, Storage

import os
from django.views import View
from django.http import HttpResponse, HttpResponseRedirect

from importlib import import_module
from django.conf import settings

SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
session = SessionStore()


class SessionStorage(Storage):
    def __init__(self):
        pass

    def get(self, key: str):
        return session.get(key, "")

    def set(self, key: str, value: str | None) -> None:
        session[key] = value

    def delete(self, key: str) -> None:
        if key in session:
            session.__delitem__(key)


client = LogtoClient(
    LogtoConfig(
        endpoint=os.environ.get("LOGTO_API_ENDPOINT"),
        appId=os.environ.get("LOGTO_API_CLIENT_ID"),
        appSecret=os.environ.get("LOGTO_API_SECRET"),
    ),
    storage=SessionStorage(),
)


class SigninView(View):
    async def get(self, request):
        uri = os.environ.get("LOGTO_API_REDIRECT_URI")
        url = await client.signIn(redirectUri=uri)
        return HttpResponseRedirect(redirect_to=url)


class CallbackView(View):
    async def get(self, request):
        absolute_uri = request.build_absolute_uri()
        try:
            await client.handleSignInCallback(absolute_uri)
            return HttpResponseRedirect(
                redirect_to=os.environ.get("LOGTO_CALLBACK_URI")
            )
        except Exception as e:
            return HttpResponse("Error: " + str(e))

urls.py

from django.urls import path

from logto_authentication.views import SigninView, CallbackView

urlpatterns = [
    path("signin/", SigninView.as_view(), name="signin"),
    path("callback/", CallbackView.as_view(), name="callback"),
]

Problem

As soon as synchronous code considered "critical" by Django (database operations with DjangoORM for the most part) is used during an asynchronous code cycle, Django will raise a SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async error.

Reference on this error : https://docs.djangoproject.com/en/5.0/topics/async/#async-safety

At that point, in the reference, we had option to set the settings to DJANGO_ALLOW_ASYNC_UNSAFE=True. Doing so make the whole process to work. But this poses a problem, as this deactivated barrier can cause data loss or corruption on other async.

Another option would have been to turn Storage synchronous calls to the session into asynchronous calls to the methods that require them. But your client is not adapted for using async methods inside this class, and we would need to create a new LogtoClient to do so.

settings.py

import os

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

Therefore, my question is : did anyone try TraditionalWebApp process with Django ? Did you find a way to make it work ?

Thanks in advance for your insights, and thanks for all the work you already did there !

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions