Skip to content

FIXING SUPABASE FOR ONCE #74

@nick-bunch

Description

@nick-bunch

The core problem (very clear)

self.supabase.get_user(token)
  • Every WebSocket connection → HTTP call to Supabase
  • WebSockets reconnect often
  • Latency + rate limits + random failures
  • Completely unnecessary

Supabase already gave you a JWT.
Your job is to verify it, not ask Supabase again.


The correct mental model

Supabase Auth = JWT issuer
Django = JWT verifier

Once the client has a JWT:

  • You never call Supabase again for auth
  • You verify the token locally
  • You trust cryptography, not network calls

What Supabase JWTs contain (important)

Supabase access tokens are standard JWTs with:

  • sub → Supabase user ID (UUID)
  • email
  • role
  • aud = "authenticated"
  • Signed with SUPABASE_JWT_SECRET

That’s all you need.


the FIX

SUPABASE_JWT_SECRET=your-supabase-jwt-secret

(From Supabase → Project Settings → API → JWT Secret)


Channels middleware (correct)

# orchard/middleware/channels.py
from urllib.parse import parse_qs
import jwt
from django.conf import settings
from channels.db import database_sync_to_async


@database_sync_to_async
def get_or_create_user(payload):
    from users.models import User

    user, _ = User.objects.get_or_create(
        supabase_id=payload["sub"],
        defaults={
            "email": payload.get("email", ""),
        },
    )
    return user


class SupabaseChannelsAuthMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        from django.contrib.auth.models import AnonymousUser

        query_string = scope.get("query_string", b"").decode()
        token = parse_qs(query_string).get("token", [None])[0]

        if not token:
            scope["user"] = AnonymousUser()
            return await self.app(scope, receive, send)

        try:
            payload = jwt.decode(
                token,
                settings.SUPABASE_JWT_SECRET,
                algorithms=["HS256"],
                audience="authenticated",
            )

            scope["user"] = await get_or_create_user(payload)

        except jwt.PyJWTError:
            scope["user"] = AnonymousUser()

        return await self.app(scope, receive, send)

✅ No network
✅ No Supabase client
✅ O(1) speed
✅ Cryptographically secure


The backend never asks the auth provider:

“Hey, is this token valid?”

It already knows — because it has the secret.


What about token refresh?

Not your problem on the backend.

Flow:

  • Client refreshes token via Supabase SDK
  • Sends new token
  • Backend verifies like always

Backend stays stateless 👍


What you should delete from your code

❌ In Channels:

SupabaseService()
self.supabase.get_user(token)

❌ Any HTTP call to Supabase for auth

❌ Storing Supabase session server-side


Final checklist

  • No Supabase SDK usage in Channels auth
  • JWT verified using jwt.decode
  • SUPABASE_JWT_SECRET present
  • User fetched/created using payload["sub"]
  • No network calls during WebSocket connect

If all are true → you fixed it properly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions