Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trouble saving user along scope #2121

Open
Alireza-Tabatabaeian opened this issue Dec 30, 2024 · 1 comment
Open

Trouble saving user along scope #2121

Alireza-Tabatabaeian opened this issue Dec 30, 2024 · 1 comment

Comments

@Alireza-Tabatabaeian
Copy link

Developing a dashboard using Django channels, two different consumers are implemented, UserConsumer and RequestConsumer.

In UserConsumer methods like login, logout and get_user are implemented. The user Authentication is being done using OTP code sent to user mobile so that a user_send_code method is in charge of sending SMS while the user_login method verifies the code sent by user. UserConsumer is implemented as follow:

class UserConsumer(AsyncJsonWebsocketConsumer):

  async def connect(self):
    await self.accept()

  async def receive_json(self, content=None, **kwargs):
    action = content.get("action", "user.get_user")
    if action == "user.login":
      await self.user_login(content)
    elif action == "user.send_code":
      await self.user_send_code(content)
    elif action == "user.logout":
      await self.user_logout()
    else:
      await self.user_get_user()

  async def user_send_code(self, content):
    await self.send("we will send sms to: {}".format(content['mobile']))
    if check_mobile_pattern(content["mobile"]):
      code = random.randint(1111, 9999)
      self.scope[VERIFY_CODE] = code
      self.scope[MOBILE] = content["mobile"]
      result = await send_sms(content["mobile"], code)
      if result:
        await self.send_json(send_code(200))
      else:
        await self.send_json(send_code(400))
    else:
      await self.send_json(send_code(SystemErrorCodes.InvalidMobilePattern))

  async def user_get_user(self):
    if self.scope['user'].is_authenticated:
      await self.send_json({"id": self.scope['user'].id})
    else:
      await self.send_json(send_code(SystemMessageCodes.AnonymousUserMessage))

  async def user_login(self, content):
    verify_code = self.scope.get("verify_code")
    code = content.get("code")
    mobile = self.scope.get(MOBILE)
    if mobile is not None and verify_code is not None and code == verify_code:
      user = await load_user_by_mobile(mobile)
      del self.scope[VERIFY_CODE]
      # login the user to this session.
      await login(self.scope, user)
      # save the session (if the session backend does not access the db you can use `sync_to_async`)
      await database_sync_to_async(self.scope["session"].save)()
      await self.send_json(send_code(200))
    else:
      await self.send_json(send_code(SystemErrorCodes.InvalidVerifyCode))

after sending the right code to web service, user logins and user_get_user method returns the userID. However when I try to open a new Socket of class RequestConsumer, the user is not set in the scope and socket closed. Here is the code:

class RequestConsumer(AsyncWebsocketConsumer):

  async def connect(self):
    self.user = self.scope['user']
    if self.user.is_authenticated:
      await self.accept()
    else:
      await self.close()
my asgi application is implemented as follow:

application = ProtocolTypeRouter({
  "http": django_asgi_app,
  "websocket": AllowedHostsOriginValidator(
    AuthMiddlewareStack(
      URLRouter(
        [
          path("user/", UserConsumer.as_asgi()),
          path('requests/', RequestConsumer.as_asgi()),
        ]
      )
    )
  ),
})

so both consumer are using same AuthMiddlewareStack middleware. And here is some parts of my settings.py file:

INSTALLED_APPS = [
  'daphne',
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'file_process.apps.FileProcessConfig',
  'channels',
  'users_app.apps.UsersAppConfig',
  'requests_apps.apps.RequestsAppConfig'
]

MIDDLEWARE = [
  'django.middleware.security.SecurityMiddleware',
  'django.middleware.common.CommonMiddleware',
  'django.middleware.csrf.CsrfViewMiddleware',
  'django.contrib.sessions.middleware.SessionMiddleware',
  'django.contrib.auth.middleware.AuthenticationMiddleware',
  'django.contrib.messages.middleware.MessageMiddleware',
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

SESSION_ENGINE = 'django.contrib.sessions.backends.db'  # Default session storage
SESSION_COOKIE_NAME = 'sessionid'  # Default cookie name
SESSION_COOKIE_HTTPONLY = True    # Prevent JavaScript access
SESSION_COOKIE_SECURE = False     # Set to True if using HTTPS
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = True

and finally this is a sample test script I've provided for the client side:

<script>
const socket1 = new WebSocket("ws://localhost:8000/user/");

socket1.onopen = () => {
  console.log("Login WebSocket Connected");
};

socket1.onerror = (error) => {
  console.error("WebSocket Error: ", error);
};

socket1.onclose = () => {
  console.log("Login WebSocket Closed");
};

socket1.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log("Message from server: ", data);

  if (data.code === 200) {

    const socket2 = new WebSocket("ws://localhost:8000/requests/");
    
    socket2.onopen = () => {
        console.log("Login WebSocket Connected");
    };

    socket2.onerror = (error) => {
        console.error("WebSocket Error: ", error);
    };

    socket2.onclose = () => {
        console.log("Login WebSocket Closed");
    };
    
  } else {
    alert("Login Failed!");
  }
};

document.getElementById("loginForm").addEventListener("submit", (e) => {
  e.preventDefault();

  const code = document.getElementById("code").value;

  const message = {
    action: "user.login",
    code: code,
  };

  socket1.send(JSON.stringify(message));
});
</script>

I couldn't find any cookie being sent to my browser if there should be one there.

This is my first webservice developed by django-channels so I'm not sure if I have done everything properly.

@carltongibson
Copy link
Member

However when I try to open a new Socket of class RequestConsumer, the user is not set in the scope and socket closed.

This sounds like you're not persisting the user to the DB session, so that it's not present in the scope for the new request.
See https://channels.readthedocs.io/en/latest/topics/authentication.html#how-to-log-a-user-in-out

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

No branches or pull requests

2 participants