Home:ALL Converter>Apollo client subscription pass JWT token handled by Django Channels middleware

Apollo client subscription pass JWT token handled by Django Channels middleware

Ask Time:2021-11-28T08:52:30         Author:Brieuc

Json Formatter

I use Graphql subscriptions with Apollo client on a Vue3 app using Django graphQL Channels and DjangoGraphqlJWT packages in my backend app.

I'm trying to pass a JWT token on the Apollo subscriptions via the connectionParams.

Following this solution. I implemented a Middleware. However Apollo is passing the connectionParams as a payload. I can't find a way to access the payload at the Middleware level, but only on the consumer.

I could access the query string property from the scope argument in the middleware. However, I can't find a way to pass a query argument after the subscription is initiated.

CLIENT SIDE:

import { setContext } from "apollo-link-context";
import { Storage } from "@capacitor/storage";

import {
  ApolloClient,
  createHttpLink,
  InMemoryCache,
  split,
} from "@apollo/client/core";
import { getMainDefinition } from "@apollo/client/utilities";
import { WebSocketLink } from "@apollo/client/link/ws";

const authLink = setContext(async (_: any, { headers }: any) => {
  const { value: authStr } = await Storage.get({ key: "auth" });

  let token;
  if (authStr) {
    const auth = JSON.parse(authStr);
    token = auth.token;
  }

  // return the headers to the context so HTTP link can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `JWT ${token}` : null,
    },
  };
});

const httpLink = createHttpLink({
  uri: process.env.VUE_APP_GRAPHQL_URL || "http://0.0.0.0:8000/graphql",
});

const wsLink = new WebSocketLink({
  uri: process.env.VUE_APP_WS_GRAPHQL_URL || "ws://0.0.0.0:8000/ws/graphql/",
  options: {
    reconnect: true,
    connectionParams: async () => {
      const { value: authStr } = await Storage.get({ key: "auth" });
      let token;
      if (authStr) {
        const auth = JSON.parse(authStr);
        token = auth.token;
        console.log(token); // So far so good the token is logged.
        return {
          token: token,
        };
      }

      return {};
    },
  },
});

const link = split(
  // split based on operation type
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === "OperationDefinition" &&
      definition.operation === "subscription"
    );
  },
  wsLink,
  httpLink
);

const cache = new InMemoryCache();
export default new ApolloClient({
  // @ts-ignore
  link: authLink.concat(link),
  cache,
});

BACKEND:

asgy.py

from tinga.routing import MyGraphqlWsConsumer
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from tinga.channels_middleware import JwtAuthMiddlewareStack
import os

from django.core.asgi import get_asgi_application
from django.conf.urls import url

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tinga.settings')

application = get_asgi_application()

# import websockets.routing

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": JwtAuthMiddlewareStack(
        URLRouter([
            url(r"^ws/graphql/$", MyGraphqlWsConsumer.as_asgi()),
        ])
    ),
})

channels_middleware.py

@database_sync_to_async
def get_user(email):
    try:
        user = User.objects.get(email=email)
        return user

    except User.DoesNotExist:
        return AnonymousUser()


class JwtAuthMiddleware(BaseMiddleware):
    def __init__(self, inner):
        self.inner = inner

    async def __call__(self, scope, receive, send):
        # Close old database connections to prevent usage of timed out connections
        close_old_connections()

        # Either find a way to get the payload from Apollo in order to get the token.
        # OR
        # Pass pass the token in query string in apollo when subscription is initiated.
        # print(scope) # query_string, headers, etc.

        # Get the token
        # decoded_data = jwt_decode(payload['token'])

        # scope["user"] = await get_user(email=decoded_data['email'])
        return await super().__call__(scope, receive, send)


def JwtAuthMiddlewareStack(inner):
    return JwtAuthMiddleware(AuthMiddlewareStack(inner))

As far as I understand, I can only access query string / URL params in the Middleware and not the Apollo payload. Would it be possible to pass the token for now in the query string? However since the token might not exist when Apollo client is provided, it needs to be reevaluated like the connectionParams.

Any workaround?

Author:Brieuc,eproduced under the CC 4.0 BY-SA copyright license with a link to the original source and this disclaimer.
Link to original article:https://stackoverflow.com/questions/70139870/apollo-client-subscription-pass-jwt-token-handled-by-django-channels-middleware
yy