graphql / graphql-playground

🎮 GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)
MIT License
8.77k stars 735 forks source link

How to send JWT token for subscription in GraphQL playground? #1296

Open tonyabracadabra opened 3 years ago

tonyabracadabra commented 3 years ago

It seems like we can only fill the header in the playground, is it the right way for passing JWT token for subscription in GraphQL playground? As websocket doesn't rely on HTTP header and that's why I am confused. It would be great to provide an example of how to pass the initial payload for subscription in GraphQL playground.

tonyabracadabra commented 3 years ago

Is there a way we can do it now? I guess I just need an answer of yes or no, and any workaround for us if we were wanting to test that feature

bizzycola commented 3 years ago

If you supply the Authorization header in the playground, it will still be sent with the websocket request, just not as an HTTP header. Rather, it will be send as the initial payload when the connection is created.

How you parse and handle that will likely depend on what backend server you are using. I was able to successfully handle authentication in ASP.Net Core with the latest version of HotChocolate(the GQL lib for asp), and pass the userId forward for use in subscription handlers, by parsing the {"Authorize": "Bearer ..."} out of the payload.

CDRayn commented 3 years ago

I'm in the same situation. I'm working on a project that uses graphql-playground as a developer tool for our GraphQL server. The playground is loaded in the browser by hitting a specific URL served by our GraphQL server. Right now, the only way to get the playground to work with the JWT based authentication of our GraphQL server is to open up the browser developer tools on a different URL copy the JWT and then open the playground and paste the JWT in the headers portion of the playground. This workflow is pretty clumsy for developers. Ideally, there would be a way to pass in the headers you want when loading the page and they populate the headers section of the playground.

bizzycola commented 3 years ago

@CDRayn You kind of can pass headers in on page load if you manually invoke playground, within the init method:

GraphQLPlayground.init(root, {
  endpoint: "urltographqlhere",
  headers: {
    Authorization: `Bearer jwttokenhere`,
  },
})

The issue, however, is that JWTs expire and playground seems to prefer loading from history. So once the JWT is set in a tab, on next page load, rather than taking that header, it'll keep the one that was previously set. I've yet to find a way to override this.

tonyabracadabra commented 3 years ago

@CDRayn You kind of can pass headers in on page load if you manually invoke playground, within the init method:

GraphQLPlayground.init(root, {
  endpoint: "urltographqlhere",
  headers: {
    Authorization: `Bearer jwttokenhere`,
  },
})

The issue, however, is that JWTs expire and playground seems to prefer loading from history. So once the JWT is set in a tab, on next page load, rather than taking that header, it'll keep the one that was previously set. I've yet to find a way to override this.

Thanks for replying, so you are saying as long as we keep the same token parsing logic on the websocket/http request, we should be able to send socket init payload by just passing the authentification tokens in the HTTP header?

bizzycola commented 3 years ago

@tonyabracadabra Yea, playground seems to send the Authorization header as the socket init payload when connecting subscriptions. When receiving the payload, it was received as {"Authorization": "Bearer ..."}.

in ASP.Net core/HotChocolate(asp graphql library), I implemented HotChocolate's ISocketSessionInterceptor, parsed the JWT out of the payload and authenticated it successfully(I then added the userID into the request pipeline so I could access it in my subscription handlers later on).

CDRayn commented 3 years ago

@CDRayn You kind of can pass headers in on page load if you manually invoke playground, within the init method:

GraphQLPlayground.init(root, {
  endpoint: "urltographqlhere",
  headers: {
    Authorization: `Bearer jwttokenhere`,
  },
})

The issue, however, is that JWTs expire and playground seems to prefer loading from history. So once the JWT is set in a tab, on next page load, rather than taking that header, it'll keep the one that was previously set. I've yet to find a way to override this.

Thank you for the suggestion! While that may still have the issue with the JWT expiring it definitely puts me on the right track. I guess you could also run the playground in a private web browser session and then open a new one when the original JWT expires. Again, thanks for you help.

estyrke commented 3 years ago

@CDRayn You kind of can pass headers in on page load if you manually invoke playground, within the init method:

GraphQLPlayground.init(root, {
  endpoint: "urltographqlhere",
  headers: {
    Authorization: `Bearer jwttokenhere`,
  },
})

The issue, however, is that JWTs expire and playground seems to prefer loading from history. So once the JWT is set in a tab, on next page load, rather than taking that header, it'll keep the one that was previously set. I've yet to find a way to override this.

One way I found to overcome this is to instead supply createApolloLink. I use React, so it looks something like this:

  const createBackendLink = (
    session: Session,
    _subscriptionEndpoint?: string
  ): { link: ApolloLink; subscriptionClient?: any } => {
    const httpLink = createHttpLink({
      uri: session.endpoint,
      headers: {
        ...(session.headers as any),
        Authorization: idToken ? `Bearer ${idToken}` : undefined,
      },
    });
    return { link: httpLink };
  };

  return (
      <Playground
        endpoint={config.backendUrl}
        fixedEndpoint={true}
        createApolloLink={createBackendLink}
      />
  );

Note that the typing information for the createApolloLink callback seems to be wrong - it must return {link: ApolloLink} and not simply ApolloLink as the typings say.