Comments (31)
I personally like performing authorization on every subscription request (in onSubscribe
). In cases where you're using token based auth, running a check on every operation request guarantees expiry detection or manipulation prevention.
from graphql-ws.
should this be another recipe?
from graphql-ws.
You can simply use the context
and inject your context before GraphQL execution. Here's how:
import { createServer } from 'graphql-ws';
createServer(
{
schema,
context: async (ctx) => {
return await getUser(ctx.connectionParams?.authorization);
},
},
{
server,
path: '/graphql',
},
);
or if you want to get the context once onConnect
and just reuse it, here is how you'd go by:
import { createServer } from 'graphql-ws';
const authorizedContexts: Record<string, unknown> = {};
createServer(
{
schema,
context: async (ctx) => {
return authorizedContexts[ctx.connectionParams?.authorization];
},
onConnect: async (ctx) => {
if (typeof ctx.connectionParams?.authorization !== 'string'){
return false;
}
const { user } = await getUser(ctx.connectionParams?.authorization);
authorizedContexts[ctx.connectionParams.authorization] = await getContext({ user });
return true;
},
},
{
server,
path: '/graphql',
},
);
from graphql-ws.
🎉 This issue has been resolved in version 1.9.0 🎉
The release is available on:
Your semantic-release bot 📦🚀
from graphql-ws.
Ah! You're absolutely right. Completely missed that... Fixed it here 222a796.
Thanks for the insight!
from graphql-ws.
I don't know what happened, but onSubscribe
never had the args
argument... Fixed #36 (comment) again.
Please consult the "ws server and client auth usage with token expiration, validation and refresh" recipe instead.
Furthermore, if you still have errors being thrown - please provide the callstack too.
from graphql-ws.
@enisdenjo I just used your previous example with context. Seems to be working great. Thanks
Here's the solution we use to create the server.
import { ApolloServer, ExpressContext } from "apollo-server-express";
import { GraphQLSchema, execute, subscribe, parse } from "graphql";
import { checkJwt } from "./auth";
import http from "http";
import WebSocket, { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
import { logError } from "./logger";
export let subscriptionServer: WebSocket.Server<WebSocket.WebSocket>;
export const createSubscriptionServer = (args: {
schema: GraphQLSchema;
apolloServer: ApolloServer<ExpressContext>;
httpServer: http.Server;
}) => {
const { schema, apolloServer, httpServer } = args;
subscriptionServer = new WebSocketServer({
path: apolloServer.graphqlPath,
server: httpServer,
});
useServer(
{
schema,
execute,
subscribe,
context: async (ctx) => {
const user = await checkJwt(ctx.connectionParams?.Authorization as string);
return { user };
},
onError: (e) => {
logError("socket_error", e);
},
},
subscriptionServer
);
};
from graphql-ws.
@OllyDixon IMO it'd be best to add an onNext
callback which checks if token is still valid, as recommended above by @enisdenjo (described here: https://github.com/enisdenjo/graphql-ws#user-content-auth-token)
from graphql-ws.
I actually don't recommend doing auth on each event emission because the amount of outgoing messages can grow real quick and soon you'll find yourself fighting with performance problems. The auth recipes has that approach displayed just as examples of what's possible.
IMHO it's perfectly fine to do auth either in onConnect
or in onSubscribe
.
As far as your concern about token expiry goes, @hckr, you can simply start a timer in onConnect
set to run after the token expires which closes the connection forcing the client to re-connect and therefore re-authenticate.
I personally do auth in onConnect
with no extra steps. The WebSocket connection cannot be hijacked while active; meaning, it's safe to assume that even if the token expires, the originally authenticated user is still the same - which is enough for me.
from graphql-ws.
I wouldnt say so. It really is that simple and everyone has their own take on authorization. Let the minds go free 😉 .
from graphql-ws.
But we could showcase how to inject a custom context for GraphQL, a PR is always welcome 😄 .
from graphql-ws.
@enisdenjo first thanks for this great library.
I can not get onSubscribe context params working though. I see that the API has changed. I am trying to do this now:
onSubscribe: async (ctx, message): Promise<ExecutionArgs> => {
// console.log('SUBSCRIBE', ctx.connectionParams);
return { contextValue: ctx.connectionParams, schema, document: null };
}
However if I add this code in onSubscribe the subscription itself does not happen. Am I missing something? Is there a way to inject context without passing all the ExecutionArgs? It seems a bit complicated to return whole graphql ExecutionArgs in order to pass context.
I am using 1.9.3 version of the lib
from graphql-ws.
Hey @tautvilas,
you're absolutely welcome!
Straight to business, you are missing the document
field. TypeScript should be complaining too though...
Complete and valid execution arguments are expected from you when returning custom ones from the onSubscribe
. Validation and all necessary steps for preparing the exec args are on your side, so make sure that the returned args have everything the execution part needs.
from graphql-ws.
This is a conscious decision because it allows you to apply optimizations (like executing some rules exclusively or trusting the client implicitly) or simply have control of when the validation happens when you introduce a custom implementation.
from graphql-ws.
If you find it helpful, there is a "Server usage with custom dynamic GraphQL arguments and validation" recipe in the readme. It should help you get started.
from graphql-ws.
Thanks for quick response!
I have tried to replicate the recipe you mentioned, but now I am stuck on the part that msg format is not the same like in recipe. In my case the msg is:
{
id: '8b5311ab-f385-4614-92dd-d657d661b0a9',
type: 'subscribe',
payload: { query: 'subscription { verificationPassed }' }
}
in the recipe should have these fields:
const args = {
schema,
contextValue: getDynamicContext(ctx, msg),
operationName: msg.payload.operationName,
document: parse(msg.payload.operationName),
variableValues: msg.payload.variables,
};
why could I be missing the operationName?
from graphql-ws.
Well, you have no operationName
field in the message payload. 😄
from graphql-ws.
I think I finally got it. The reason I got really confused because there is a bug in the recipe (I think). It should be:
operationName: msg.payload.operationName,
document: parse(msg.payload.query),
The recipe says that operationName has to be parsed and not query
from graphql-ws.
None of the solutions here seem to pass the TypeScript test, seems they are returning the wrong information. Did the API change? Could someone provide a full example; I can't seem to find it :-/
For example where are you getting getDynamicContext
from?
Any ideas here?
from graphql-ws.
@OllyDixon the onSubscribe
API never changed, it returns either an array of GraphQLError
s or the ExecutionArgs
, you're not returning either.
from graphql-ws.
For example where are you getting getDynamicContext from?
That's just an example of a function you can implement that returns the context dynamically from the arguments you pass in.
from graphql-ws.
@enisdenjo thanks for the speedy reply, out full example doesn't seem to work though :-/
// onConnect: async (connection) => {
// const auth = connection?.connectionParams!["Authorization"] ?? "";
// if (!auth) {
// return {};
// }
// // @ts-ignore
// const user = await checkJwt(auth);
// return { user };
// },
// @ts-ignore
onSubscribe: async (ctx, msg, args) => {
const auth = ctx?.connectionParams!["Authorization"] ?? "";
const user = await checkJwt(auth);
const newArgs: any = {
schema,
contextValue: ctx,
operationName: msg.payload.operationName,
document: parse(msg.payload.operationName),
variableValues: msg.payload.variables,
user,
};
return newArgs;
},
from graphql-ws.
@OllyDixon what does "doesn't seem to work" mean?
P.S. my example in #36 (comment) was wrong - it's fixed now.
from graphql-ws.
@enisdenjo it throws an error
onSubscribe: async (ctx, msg, args) => {
const user = await checkJwt(ctx.connectionParams?.Authorization);
return { ...args, contextValue: { user } };
},
Internal error occurred during message handling. Please check your implementation. TypeError: Cannot read properties of undefined (reading 'definitions')
from graphql-ws.
@OllyDixon In your example, is context
function called on client connection or on each subscription?
from graphql-ws.
@OllyDixon In your example, is
context
function called on client connection or on each subscription?
We only subscribe once and send "events" to the client. Each event as a "type" and "object" that is parsed.
I wouldn't recommend having multiple subscriptions.
from graphql-ws.
@OllyDixon Right. But my specific question is does your code example handle the case in which a JWT token was valid when the client subscribed and then is expired after, let's say, 30 minutes – is the client disconnected / re-authenticated in any way?
from graphql-ws.
@hckr Interesting edge case. The client will just reconnect then, once per hour blip. We have an event bus API that handles each device and potential missed socket events.
from graphql-ws.
@OllyDixon But AFAIR the [malicious] client doesn't have to disconnect and the session (context) will remain until the socket is closed.
Note that the context function is invoked on each operation only once. Meaning, for subscriptions, only at the point of initialising the subscription; not on every subscription event emission.
(https://github.com/enisdenjo/graphql-ws/blob/d47e1bb5ff5589bf49587e70b446a2cab01688d1/docs/interfaces/server.ServerOptions.md#context)
from graphql-ws.
@hckr interesting, guess we'll need to disconnect/connect each hour hehe :-D
from graphql-ws.
@hckr thanks, will do :-)
from graphql-ws.
Related Issues (20)
- Allow to update connectionParams
- fix incorrect Bad Gateway error code HOT 3
- Subscriptions not working with Hasura via apollo Client graphql. error - Error: WebSocket implementation missing; HOT 1
- rename onMessage in server to postMessage, withMessage (or something more appropriate) HOT 1
- How to use `graphql-ws` protocol?
- Websocket using bun server - Unable to access request object in ctx.extras
- Warning Workspaces can only be enabled in private projects. HOT 3
- Detect subscription successfully started
- Errors during streaming subscriptions aren't properly handled or emitted HOT 1
- httpOnly cookie authentication
- Promises getting rejected with non-Error objects HOT 2
- Some subscriptions remain open after a websocket has been closed HOT 2
- Getting "Internal error occurred during message handling. Please check your implementation." after following recipe
- Clarify Contribution Policy
- Clients can be sent an `Error` message after cancellation. HOT 2
- How to avoid logging of hole object in graphql-ws?
- Consider migrating to crossws
- Make code and reason optional when closing a connection HOT 2
- fastify websockets breaking change HOT 4
- ERROR CREATING WsLink
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-ws.