django_sockets

Django Sockets

PyPI version License: MIT

Simplified Django websocket processes designed to work with cloud caches (valkey|redis on single|distributed|serverless)

Setup

General

Make sure you have Python 3.10.x (or higher) installed on your system. You can download it here.

Installation

pip install django_sockets

Other Requirements

  • Redis / Valkey Cache Server: If you plan to broadcast messages across clients and not just respond to individual clients, make sure a cache (valkey or redis) is setup and accessible from your server.

    Expand this to setup a local valkey cache using Docker.

    • Install Docker: https://docs.docker.com/get-docker/
    • Create and start a valkey cache server using docker:

      docker run -d -p 6379:6379 --name django_sockets_cache valkey/valkey:7
      
    • To run the container after it has been stopped:

      docker start django_sockets_cache
      
    • To kill the container later:

      docker kill django_sockets_cache
      

Usage

Examples

Example: Simple Counter

  1. Make sure a redis / valkey cache server is running.
  2. Install Requirements:

    shell

    pip install django_sockets
    
    • Note: This would normally be done via your requirements.txt file and installed in a virtual environment.
  3. Create a new Django project (if you don't already have one) and navigate to the project directory:

    shell

    python3 -m django startproject myapp
    cd myapp
    
  4. Modify your settings file:

    • Add ASGI_APPLICATION above your INSTALLED_APPS
    • Add 'daphne' to the top of your INSTALLED_APPS in your settings.py file
      • Daphne is the django created ASGI server that is used by django_sockets.

    myapp/settings.py

    ASGI_APPLICATION = 'myapp.asgi.application'
    INSTALLED_APPS = [
        'daphne',
        # Your other installed apps
        ]
    
  5. Create a new file called ws.py and place it in myapp.

    • This file will hold the websocket server logic.
    • Define a SocketServer class that extends BaseSocketServer.
      • Define a configure method to set the cache hosts.
      • Define a connect method to handle logic when a client connects.
      • Define a receive method to handle logic when a client sends data.
    • Define a get_ws_asgi_application function that returns a URL Router with the websocket routes.
      • This is where you can apply any needed middleware.

    myapp/ws.py

    from django.urls import path
    from django_sockets.middleware import SessionAuthMiddleware
    from django_sockets.sockets import BaseSocketServer
    from django_sockets.utils import URLRouter
    
    
    class SocketServer(BaseSocketServer):
        def configure(self):
            '''
            This method is optional and only needs to be defined 
            if you are broadcasting or subscribing to channels.
    
            It is not required if you just plan to respond to
            individual websocket clients.
    
            This method is used during the initialization of the
            socket server to define the cache hosts that will be
            used for broadcasting and subscribing to channels.
            '''
            self.hosts = [{"address": "redis://0.0.0.0:6379"}]
    
        def connect(self):
            '''
            This method is optional and is called when a websocket
            client connects to the server. 
    
            It can be used for a variety of purposes such as 
            subscribing to a channel.
            '''
            # When a client connects, create a channel_id attribute 
            # that is set to the user's id. This allows for user scoped 
            # channels if you are using auth middleware.
            # Note: Since we are not using authentication, all 
            # clients will be subscribed to the same channel ('None').
            self.channel_id = str(self.scope['user'].id)
            self.subscribe(self.channel_id)
    
        def receive(self, data):
            '''
            This method is called when a websocket client sends
            data to the server. It can be used to:
                - Execute Custom Logic
                - Update the state of the server
                - Send data back to the client
                - Subscribe to a channel
                - Broadcast data to be sent to subscribed clients
            '''
            if data.get('command')=='reset':
                data['counter']=0
            elif data.get('command')=='increment':
                data['counter']+=1
            else:
                raise ValueError("Invalid command")
            # Broadcast the update to all websocket clients 
            # subscribed to this socket's channel_id
            self.broadcast(self.channel_id, data)
            # Alternatively if you just want to respond to the 
            # current socket client, just use self.send(data):
            # self.send(data)
    
    
    def get_ws_asgi_application():
        '''
        Define the websocket routes for the Django application.
    
        You can have multiple websocket routes defined here.
    
        This is the place to apply any needed middleware.
        '''
        # Note: `SessionAuthMiddleware` is not required, but is useful 
        # for user scoped channels.
        return SessionAuthMiddleware(URLRouter([
            path("ws/", SocketServer.as_asgi),
        ]))
    
  6. Modify your asgi.py file:

    • Use the django_sockets ProtocolTypeRouter
    • Based on the protocol type, return the appropriate ASGI application.

    myapp/asgi.py

    import os
    
    from django.core.asgi import get_asgi_application
    from django_sockets.utils import ProtocolTypeRouter
    from .ws import get_ws_asgi_application
    
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings')
    
    asgi_app = get_asgi_application()
    ws_asgi_app = get_ws_asgi_application()
    
    application = ProtocolTypeRouter(
        {
            "http": asgi_app,
            "websocket": ws_asgi_app,
        }
    )
    
  7. In the project root, create templates/client.html:

    • This will be the client side of the websocket connection.
    • It will contain a simple counter that can be incremented and reset.
    • The client will send commands to the server to reset or increment the counter.
    • The server will handle the commands and broadcast or send the updated counter relevant clients.

    templates/client.html

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>WebSocket Client</title>
        </head>
        <body>
            <h1>WebSocket Client</h1>
            <h2>User: {{ user.username }}</h2>
            <div>
                <button id="resetBtn">Reset Counter</button>
                <button id="incrementBtn">Increment Counter</button>
            </div>
            <div>
                <h3>Messages:</h3>
                <pre id="messages"></pre>
            </div>
    
            <script>
                // Connect to the WebSocket server
                const wsUrl = "ws://localhost:8000/ws/";
                const websocket = new WebSocket(wsUrl);
                var counter = 0;
    
                // DOM elements
                const messages = document.getElementById("messages");
                const resetBtn = document.getElementById("resetBtn");
                const incrementBtn = document.getElementById("incrementBtn");
    
                // Helper function to display messages
                const displayMessage = (msg) => {
                    messages.textContent += msg + "
    ";
                };
    
                // Handle WebSocket events
                websocket.onopen = () => {
                    displayMessage("WebSocket connection established.");
                };
    
                websocket.onmessage = (event) => {
                    displayMessage("Received: " + event.data);
                    counter = JSON.parse(event.data).counter;
                };
    
                websocket.onerror = (error) => {
                    displayMessage("WebSocket error: " + error);
                };
    
                websocket.onclose = () => {
                    displayMessage("WebSocket connection closed.");
                };
    
                // Send 'reset' command
                resetBtn.addEventListener("click", () => {
                    const command = { command: "reset" };
                    websocket.send(JSON.stringify(command));
                    displayMessage("Sent: " + JSON.stringify(command));
                });
    
                // Send 'increment' command
                incrementBtn.addEventListener("click", () => {
                    const command = { "command": "increment", "counter": counter };
                    websocket.send(JSON.stringify(command));
                    displayMessage("Sent: " + JSON.stringify(command));
                });
            </script>
        </body>
        </html>
    
  8. In settings.py:

    • Update DIRS in your TEMPLATES to include your new template directory

    myapp/settings.py

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [BASE_DIR / 'templates'], # Modify this line
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
  9. In urls.py:

    • Add a simple clent_view to render the client.html template
    • Set at it the root URL

    myapp/urls.py

    from django.contrib import admin
    from django.shortcuts import render
    from django.urls import path
    
    def client_view(request):
        '''
        Render the client.html template
        '''
        # Pass the user to the client.html template
        return render(request, 'client.html', {'user': request.user})
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', client_view),
    ]
    
    • Note: Normally something like client_view would be imported from a views.py file, but for simplicity it is defined here.
  10. Setup and run the server:

    • Make any needed migrations (determine if the database needs to be created or updated)
    • Migrate any changes to bring the database up to date
    • Run the server

    shell

    python manage.py makemigrations
    python manage.py migrate
    python manage.py runserver
    
  11. Open your browser:

    • Navigate to http://localhost:8000/ to see the client page.
    • Duplicate the tab.
      • You should see the counter incrementing and resetting in both tabs.
    • Note: The counter state is maintained client side.
      • If one tab joins after the other has modified the counter, it will not be in sync.
      • Whichever counter fires first will determine the next counter value for both tabs.
    • Note: Since you have not logged in yet, your Auth Middleware will just return an Anonymous User.
      • This means that all users are subscribed to the same channel from the user id ('None').
      • Once users are logged in, they will be subscribed to their own user id channel.
  12. To avoid creating a custom login page, we will just use a superuser and take advantage of the admin login page.

    • To create a superuser, you can run the following command:

      python manage.py createsuperuser
      
      • Follow the prompts to create a superuser.
    • Login at http://localhost:8000/admin/login/?next=/ with your superuser credentials.
      • You can logout by navigating to http://localhost:8000/admin/ and clicking the logout button.
    • You should now see a functional counter page with websockets scoped to the logged in user.




Example: Simple Counter Extension

Use DjangoRestFramework for Token Authentication instead of Session based Authentication

  1. Complete all steps in the previous example.
  2. Install DjangoRestFramework:

    shell

    pip install djangorestframework
    
  3. Modify your settings.py file:

    • Add 'rest_framework.authtoken' to the end of your INSTALLED_APPS

    myapp/settings.py

    INSTALLED_APPS = [
        'daphne',
        # Your other installed apps,
        'rest_framework.authtoken', # Add this installed app
        ]
    
  4. Make and run migrations:

    shell

    python manage.py makemigrations
    python manage.py migrate
    
  5. In your view (specified in myapp.urls.py):

    • Ensure you have a DRF Token and pass it to your websocket template.
    • Force users to login before accessing the websocket client.
      • In general, you would want to create a custom login page and use the @login_required decorator on your view.
      • For simplicity, we are just using the admin login page. myapp/urls.py
    from django.contrib import admin
    from django.shortcuts import render
    from django.urls import path
    
    from rest_framework.authtoken.models import Token # Add this import
    from django.contrib.auth.decorators import login_required # Add this import
    
    @login_required(login_url="/admin/login/") # Add this decorator
    def client_view(request):
        '''
        Render the client.html template
        '''
        # Get or create a token for the user
        token, created = Token.objects.get_or_create(user=request.user) # Add this line
        # Pass the user and token to the client.html template
        return render(request, 'client.html', {'user': request.user, 'token': token}) # Modify this line
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', client_view),
    ]
    
  6. Update your middleware to use the DRFTokenAuthMiddleware instead of the SessionAuthMiddleware:

    myapp/ws.py

    from django.urls import path
    from django_sockets.middleware import DRFTokenAuthMiddleware # Modify this line
    from django_sockets.sockets import BaseSocketServer
    from django_sockets.utils import URLRouter
    
    # Your existing code here
    
    def get_ws_asgi_application():
        '''
        Define the websocket routes for the Django application.
    
        You can have multiple websocket routes defined here.
    
        This is the place to apply any needed middleware.
        '''
        return DRFTokenAuthMiddleware(URLRouter([ # Modify this line
            path("ws/", SocketServer.as_asgi),
        ]))
    
  7. Update your client to pass the token to the websocket server on connection:

    • Option 1: Use a sec-websocket-protocol header to pass the token:

      templates/client.html

      const websocket = new WebSocket(wsUrl,["Token.{{ token }}"]);
      
    • Option 2: Use a query parameter to pass the token:

      templates/client.html

      const wsUrl = "ws://localhost:8000/ws/?token={{ token }}";
      const websocket = new WebSocket(wsUrl);
      
  8. Run the server and navigate to http://localhost:8000/ to see the client page.

    • You will be redirected to the admin login page.
    • Login with your superuser credentials.
    • You should now see a functional counter page with websockets scoped to the logged in user.




  1"""
  2# Django Sockets
  3[![PyPI version](https://badge.fury.io/py/django_sockets.svg)](https://badge.fury.io/py/django_sockets)
  4[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
  5
  6Simplified Django websocket processes designed to work with cloud caches (valkey|redis on single|distributed|serverless)
  7
  8# Setup
  9
 10### General
 11
 12Make sure you have Python 3.10.x (or higher) installed on your system. You can download it [here](https://www.python.org/downloads/).
 13
 14### Installation
 15
 16```
 17pip install django_sockets
 18```
 19
 20### Other Requirements
 21
 22- <b>Redis / Valkey Cache Server</b>: If you plan to `broadcast` messages across clients and not just respond to individual clients, make sure a cache (valkey or redis) is setup and accessible from your server. 
 23    <details>
 24    <summary>Expand this to setup a local valkey cache using Docker.</summary>
 25
 26    - Install Docker: https://docs.docker.com/get-docker/
 27    - Create and start a valkey cache server using docker:
 28        ```bash
 29        docker run -d -p 6379:6379 --name django_sockets_cache valkey/valkey:7
 30        ```
 31    - To run the container after it has been stopped:
 32        ```bash
 33        docker start django_sockets_cache
 34        ```
 35    - To kill the container later:
 36        ```bash
 37        docker kill django_sockets_cache
 38        ```
 39    </details>
 40
 41# Usage
 42
 43- Low level docs: https://connor-makowski.github.io/django_sockets/django_sockets.html
 44- [Working django and non django examples can be found here](https://github.com/connor-makowski/django_sockets/tree/main/examples).
 45
 46## Examples
 47
 48### Example: Simple Counter
 49
 501. Make sure a redis / valkey cache server is running.
 512. Install Requirements:
 52
 53    `shell`
 54    ```bash
 55    pip install django_sockets
 56    ```
 57    - Note: This would normally be done via your `requirements.txt` file and installed in a virtual environment.
 583. Create a new Django project (if you don't already have one) and navigate to the project directory:
 59
 60    `shell`
 61    ```sh
 62    python3 -m django startproject myapp
 63    cd myapp
 64    ```
 654. Modify your settings file:
 66    - Add `ASGI_APPLICATION` above your `INSTALLED_APPS`
 67    - Add `'daphne'` to the top of your `INSTALLED_APPS` in your `settings.py` file
 68        - Daphne is the django created ASGI server that is used by `django_sockets`.
 69    
 70    `myapp/settings.py`
 71    ```py
 72    ASGI_APPLICATION = 'myapp.asgi.application'
 73    INSTALLED_APPS = [
 74        'daphne',
 75        # Your other installed apps
 76        ]
 77    ```
 785. Create a new file called `ws.py` and place it in `myapp`.
 79    - This file will hold the websocket server logic.
 80    - Define a `SocketServer` class that extends `BaseSocketServer`.
 81        - Define a `configure` method to set the cache hosts.
 82        - Define a `connect` method to handle logic when a client connects.
 83        - Define a `receive` method to handle logic when a client sends data.
 84    - Define a `get_ws_asgi_application` function that returns a URL Router with the websocket routes.
 85        - This is where you can apply any needed middleware.
 86
 87    `myapp/ws.py`
 88    ```py
 89    from django.urls import path
 90    from django_sockets.middleware import SessionAuthMiddleware
 91    from django_sockets.sockets import BaseSocketServer
 92    from django_sockets.utils import URLRouter
 93
 94        
 95    class SocketServer(BaseSocketServer):
 96        def configure(self):
 97            '''
 98            This method is optional and only needs to be defined 
 99            if you are broadcasting or subscribing to channels.
100
101            It is not required if you just plan to respond to
102            individual websocket clients.
103
104            This method is used during the initialization of the
105            socket server to define the cache hosts that will be
106            used for broadcasting and subscribing to channels.
107            '''
108            self.hosts = [{"address": "redis://0.0.0.0:6379"}]
109
110        def connect(self):
111            '''
112            This method is optional and is called when a websocket
113            client connects to the server. 
114            
115            It can be used for a variety of purposes such as 
116            subscribing to a channel.
117            '''
118            # When a client connects, create a channel_id attribute 
119            # that is set to the user's id. This allows for user scoped 
120            # channels if you are using auth middleware.
121            # Note: Since we are not using authentication, all 
122            # clients will be subscribed to the same channel ('None').
123            self.channel_id = str(self.scope['user'].id)
124            self.subscribe(self.channel_id)
125
126        def receive(self, data):
127            '''
128            This method is called when a websocket client sends
129            data to the server. It can be used to:
130                - Execute Custom Logic
131                - Update the state of the server
132                - Send data back to the client
133                - Subscribe to a channel
134                - Broadcast data to be sent to subscribed clients
135            '''
136            if data.get('command')=='reset':
137                data['counter']=0
138            elif data.get('command')=='increment':
139                data['counter']+=1
140            else:
141                raise ValueError("Invalid command")
142            # Broadcast the update to all websocket clients 
143            # subscribed to this socket's channel_id
144            self.broadcast(self.channel_id, data)
145            # Alternatively if you just want to respond to the 
146            # current socket client, just use self.send(data):
147            # self.send(data)
148
149
150    def get_ws_asgi_application():
151        '''
152        Define the websocket routes for the Django application.
153
154        You can have multiple websocket routes defined here.
155
156        This is the place to apply any needed middleware.
157        '''
158        # Note: `SessionAuthMiddleware` is not required, but is useful 
159        # for user scoped channels.
160        return SessionAuthMiddleware(URLRouter([
161            path("ws/", SocketServer.as_asgi),
162        ]))
163    ```
1646. Modify your `asgi.py` file:
165    - Use the `django_sockets` `ProtocolTypeRouter`
166    - Based on the protocol type, return the appropriate ASGI application.
167
168    `myapp/asgi.py`
169    ```py
170    import os
171
172    from django.core.asgi import get_asgi_application
173    from django_sockets.utils import ProtocolTypeRouter
174    from .ws import get_ws_asgi_application
175
176    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings')
177
178    asgi_app = get_asgi_application()
179    ws_asgi_app = get_ws_asgi_application()
180
181    application = ProtocolTypeRouter(
182        {
183            "http": asgi_app,
184            "websocket": ws_asgi_app,
185        }
186    )
187    ```
1887. In the project root, create `templates/client.html`:
189    - This will be the client side of the websocket connection.
190    - It will contain a simple counter that can be incremented and reset.
191    - The client will send commands to the server to reset or increment the counter.
192    - The server will handle the commands and broadcast or send the updated counter relevant clients.
193
194    `templates/client.html`
195    ```html
196    <!DOCTYPE html>
197    <html lang="en">
198    <head>
199        <meta charset="UTF-8">
200        <meta name="viewport" content="width=device-width, initial-scale=1.0">
201        <title>WebSocket Client</title>
202    </head>
203    <body>
204        <h1>WebSocket Client</h1>
205        <h2>User: {{ user.username }}</h2>
206        <div>
207            <button id="resetBtn">Reset Counter</button>
208            <button id="incrementBtn">Increment Counter</button>
209        </div>
210        <div>
211            <h3>Messages:</h3>
212            <pre id="messages"></pre>
213        </div>
214
215        <script>
216            // Connect to the WebSocket server
217            const wsUrl = "ws://localhost:8000/ws/";
218            const websocket = new WebSocket(wsUrl);
219            var counter = 0;
220
221            // DOM elements
222            const messages = document.getElementById("messages");
223            const resetBtn = document.getElementById("resetBtn");
224            const incrementBtn = document.getElementById("incrementBtn");
225
226            // Helper function to display messages
227            const displayMessage = (msg) => {
228                messages.textContent += msg + "\n";
229            };
230
231            // Handle WebSocket events
232            websocket.onopen = () => {
233                displayMessage("WebSocket connection established.");
234            };
235
236            websocket.onmessage = (event) => {
237                displayMessage("Received: " + event.data);
238                counter = JSON.parse(event.data).counter;
239            };
240
241            websocket.onerror = (error) => {
242                displayMessage("WebSocket error: " + error);
243            };
244
245            websocket.onclose = () => {
246                displayMessage("WebSocket connection closed.");
247            };
248
249            // Send 'reset' command
250            resetBtn.addEventListener("click", () => {
251                const command = { command: "reset" };
252                websocket.send(JSON.stringify(command));
253                displayMessage("Sent: " + JSON.stringify(command));
254            });
255
256            // Send 'increment' command
257            incrementBtn.addEventListener("click", () => {
258                const command = { "command": "increment", "counter": counter };
259                websocket.send(JSON.stringify(command));
260                displayMessage("Sent: " + JSON.stringify(command));
261            });
262        </script>
263    </body>
264    </html>
265    ```
266
2678. In `settings.py`:
268    - Update `DIRS` in your `TEMPLATES` to include your new template directory
269
270    `myapp/settings.py`
271    ```py
272    TEMPLATES = [
273        {
274            'BACKEND': 'django.template.backends.django.DjangoTemplates',
275            'DIRS': [BASE_DIR / 'templates'], # Modify this line
276            'APP_DIRS': True,
277            'OPTIONS': {
278                'context_processors': [
279                    'django.template.context_processors.debug',
280                    'django.template.context_processors.request',
281                    'django.contrib.auth.context_processors.auth',
282                    'django.contrib.messages.context_processors.messages',
283                ],
284            },
285        },
286    ]
287    ```
288
2899. In `urls.py`:
290    - Add a simple `clent_view` to render the `client.html` template
291    - Set at it the root URL
292
293    `myapp/urls.py`
294    ```py
295    from django.contrib import admin
296    from django.shortcuts import render
297    from django.urls import path
298
299    def client_view(request):
300        '''
301        Render the client.html template
302        '''
303        # Pass the user to the client.html template
304        return render(request, 'client.html', {'user': request.user})
305
306    urlpatterns = [
307        path('admin/', admin.site.urls),
308        path('', client_view),
309    ]
310    ```
311    - Note: Normally something like `client_view` would be imported from a `views.py` file, but for simplicity it is defined here.
312
31310. Setup and run the server:
314    - Make any needed migrations (determine if the database needs to be created or updated)
315    - Migrate any changes to bring the database up to date
316    - Run the server
317
318    `shell`
319    ```sh
320    python manage.py makemigrations
321    python manage.py migrate
322    python manage.py runserver
323    ```
32411. Open your browser:
325    - Navigate to `http://localhost:8000/` to see the client page. 
326    - Duplicate the tab. 
327        - You should see the counter incrementing and resetting in both tabs.
328    - Note: The counter state is maintained client side. 
329        - If one tab joins after the other has modified the counter, it will not be in sync.
330        - Whichever counter fires first will determine the next counter value for both tabs.
331    - Note: Since you have not logged in yet, your Auth Middleware will just return an Anonymous User.
332        - This means that all users are subscribed to the same channel from the user id ('None').
333        - Once users are logged in, they will be subscribed to their own user id channel.
33412. To avoid creating a custom login page, we will just use a superuser and take advantage of the admin login page.
335    - To create a superuser, you can run the following command:
336        ```bash
337        python manage.py createsuperuser
338        ```
339        - Follow the prompts to create a superuser.
340    - Login at `http://localhost:8000/admin/login/?next=/` with your superuser credentials.
341        - You can logout by navigating to `http://localhost:8000/admin/` and clicking the logout button.
342    - You should now see a functional counter page with websockets scoped to the logged in user.
343
344<br/><hr/><br/>
345
346### Example: Simple Counter Extension 
347#### Use DjangoRestFramework for Token Authentication instead of Session based Authentication
348
3491. Complete all steps in the previous example.
3502. Install DjangoRestFramework:
351
352    `shell`
353    ```bash
354    pip install djangorestframework
355    ```
3563. Modify your `settings.py` file:
357    - Add `'rest_framework.authtoken'` to the end of your `INSTALLED_APPS`
358
359    `myapp/settings.py`
360    ```py
361    INSTALLED_APPS = [
362        'daphne',
363        # Your other installed apps,
364        'rest_framework.authtoken', # Add this installed app
365        ]
366    ```
3674. Make and run migrations:
368
369    `shell`
370    ```bash
371    python manage.py makemigrations
372    python manage.py migrate
373    ```
3745. In your view (specified in `myapp.urls.py`):
375    - Ensure you have a DRF Token and pass it to your websocket template.
376    - Force users to login before accessing the websocket client.
377        - In general, you would want to create a custom login page and use the `@login_required` decorator on your view. 
378        - For simplicity, we are just using the admin login page.
379    `myapp/urls.py`
380    ```py
381    from django.contrib import admin
382    from django.shortcuts import render
383    from django.urls import path
384
385    from rest_framework.authtoken.models import Token # Add this import
386    from django.contrib.auth.decorators import login_required # Add this import
387
388    @login_required(login_url="/admin/login/") # Add this decorator
389    def client_view(request):
390        '''
391        Render the client.html template
392        '''
393        # Get or create a token for the user
394        token, created = Token.objects.get_or_create(user=request.user) # Add this line
395        # Pass the user and token to the client.html template
396        return render(request, 'client.html', {'user': request.user, 'token': token}) # Modify this line
397
398    urlpatterns = [
399        path('admin/', admin.site.urls),
400        path('', client_view),
401    ]
402    ```
4036. Update your middleware to use the `DRFTokenAuthMiddleware` instead of the `SessionAuthMiddleware`:
404
405    `myapp/ws.py`
406    ```py
407    from django.urls import path
408    from django_sockets.middleware import DRFTokenAuthMiddleware # Modify this line
409    from django_sockets.sockets import BaseSocketServer
410    from django_sockets.utils import URLRouter
411
412    # Your existing code here
413
414    def get_ws_asgi_application():
415        '''
416        Define the websocket routes for the Django application.
417
418        You can have multiple websocket routes defined here.
419
420        This is the place to apply any needed middleware.
421        '''
422        return DRFTokenAuthMiddleware(URLRouter([ # Modify this line
423            path("ws/", SocketServer.as_asgi),
424        ]))
425    ```
426
4277. Update your client to pass the token to the websocket server on connection:
428    - Option 1: Use a `sec-websocket-protocol` header to pass the token:
429        
430        `templates/client.html`
431        ```html
432        const websocket = new WebSocket(wsUrl,["Token.{{ token }}"]);
433        ```
434    - Option 2: Use a query parameter to pass the token:
435        
436        `templates/client.html`
437        ```html
438        const wsUrl = "ws://localhost:8000/ws/?token={{ token }}";
439        const websocket = new WebSocket(wsUrl);
440        ```
4418. Run the server and navigate to `http://localhost:8000/` to see the client page.
442    - You will be redirected to the admin login page.
443    - Login with your superuser credentials.
444    - You should now see a functional counter page with websockets scoped to the logged in user.
445
446<br/><hr/><br/>"""